Пример #1
0
    def run(self):
        """Scans for signed transactions and updates status if multisig threshold achieved"""
        self.logger.info("Starting..")

        to_block = w3.eth.blockNumber - self.config.eth_confirmations

        self.catch_up(to_block)

        self.event_listener.start()
        self.logger.info("Done catching up")

        while not self.stop_signal.is_set():
            for transaction in Swap.objects(status=Status.SWAP_RETRY):
                self._retry(transaction)

            for transaction in Swap.objects(status=Status.SWAP_UNSIGNED):
                self.logger.debug(f"Checking unsigned tx {transaction.id}")
                if Signatures.objects(tx_id=transaction.id).count(
                ) >= self.config.signatures_threshold:
                    self.logger.info(
                        f"Found tx {transaction.id} with enough signatures to broadcast"
                    )
                    transaction.status = Status.SWAP_SIGNED
                    transaction.save()
                    self.logger.info(
                        f"Set status of tx {transaction.id} to signed")
                else:
                    self.logger.debug(
                        f"Tx {transaction.id} does not have enough signatures")
            self.stop_signal.wait(self.config.sleep_interval)
Пример #2
0
    def _create_and_broadcast(self, tx: Swap):
        # reacts to signed tx in the DB that are ready to be sent to secret20
        signatures = [
            signature.signed_tx
            for signature in Signatures.objects(tx_id=tx.id)
        ]
        if len(signatures
               ) < self.config['signatures_threshold']:  # sanity check
            self.logger.error(
                msg=f"Tried to sign tx {tx.id}, without enough signatures"
                f" (required: {self.config['signatures_threshold']}, have: {len(signatures)})"
            )
            return

        try:
            signed_tx = self._create_multisig(tx.unsigned_tx, signatures)
            scrt_tx_hash = self._broadcast(signed_tx)
            self.logger.info(
                f"Broadcasted {tx.id} successfully - {scrt_tx_hash}")
            tx.status = Status.SWAP_SUBMITTED
            tx.dst_tx_hash = scrt_tx_hash
            tx.save()
            self.logger.info(f"Changed status of tx {tx.id} to submitted")
        except (RuntimeError, OperationError) as e:
            self.logger.error(
                msg=f"Failed to create multisig and broadcast, error: {e}")
Пример #3
0
 def _retry(self, tx: Swap):
     for signature in Signatures.objects(tx_id=tx.id):
         signature.delete()
     tx.status = Status.SWAP_UNSIGNED
     tx.sequence = self.sequence
     tx.save()
     self.sequence = self.sequence + 1
Пример #4
0
    def _scan_swap(self):
        while not self.stop_event.is_set():
            failed_prev = False
            for tx in Commands.objects(status=Status.SWAP_SIGNED):
                self._create_and_broadcast(tx)

            for tx in Swap.objects(status=Status.SWAP_SIGNED,
                                   src_network="Ethereum"):
                # if there are 2 transactions that depend on each other (sequence number), and the first fails we mark
                # the next as "retry"
                if failed_prev:
                    self.logger.info(f"Previous TX failed, retrying {tx.id}")
                    _set_retry(tx)
                    continue

                self.logger.info(f"Found tx ready for broadcasting {tx.id}")
                failed_prev = not self._create_and_broadcast(tx)
            failed_prev = False
            for tx in Swap.objects(status=Status.SWAP_SUBMITTED,
                                   src_network="Ethereum"):
                if failed_prev:
                    self.logger.info(f"Previous TX failed, retrying {tx.id}")
                    _set_retry(tx)
                    continue
                failed_prev = not self._broadcast_validation(tx)

            self.logger.debug('done scanning for swaps. sleeping..')
            self.stop_event.wait(self.config.sleep_interval)
Пример #5
0
 def _scan_swap(self):
     while not self.stop_event.is_set():
         for tx in Swap.objects(status=Status.SWAP_SIGNED):
             self.logger.info(f"Found tx ready for broadcasting {tx.id}")
             self._create_and_broadcast(tx)
             sleep(SCRT_BLOCK_TIME)
         for tx in Swap.objects(status=Status.SWAP_SUBMITTED):
             self._broadcast_validation(tx)
         self.logger.debug('done scanning for swaps. sleeping..')
         self.stop_event.wait(self.config['sleep_interval'])
Пример #6
0
    def _handle(self, event: AttributeDict):
        """Extracts tx data from @event and add unsigned_tx to db"""
        if not self.contract.verify_destination(event):
            return

        amount = str(self.contract.extract_amount(event))

        try:
            block_number, tx_hash, recipient, token = self.contract.parse_swap_event(
                event)
            if token is None:
                token = 'native'
        except ValueError:
            return

        try:
            s20 = self._get_s20(token)
            mint = mint_json(amount, tx_hash, recipient, s20.address)
            unsigned_tx = create_unsigned_tx(self.config.scrt_swap_address,
                                             mint, self.config.chain_id,
                                             self.config.enclave_key,
                                             self.config.swap_code_hash,
                                             self.multisig.address)

            tx = Swap(src_tx_hash=tx_hash,
                      status=Status.SWAP_UNSIGNED,
                      unsigned_tx=unsigned_tx,
                      src_coin=token,
                      dst_coin=s20.name,
                      dst_address=s20.address,
                      src_network="Ethereum",
                      sequence=self.sequence,
                      amount=amount)
            tx.save(force_insert=True)
            self.sequence = self.sequence + 1
            self.logger.info(
                f"saved new Ethereum -> Secret transaction {tx_hash}, for {amount} {s20.name}"
            )
            # SwapTrackerObject.update_last_processed(src=Source.ETH.value, update_val=block_number)
        except (IndexError, AttributeError, KeyError) as e:
            self.logger.error(f"Failed on tx {tx_hash}, block {block_number}, "
                              f"due to error: {e}")
        except RuntimeError as e:
            self.logger.error(
                f"Failed to create swap tx for eth hash {tx_hash}, block {block_number}. Error: {e}"
            )
        except NotUniqueError as e:
            self.logger.error(
                f"Tried to save duplicate TX, might be a catch up issue - {e}")
        # return block_number, tx_hash, recipient, s20
        SwapTrackerObject.update_last_processed('Ethereum', block_number)
Пример #7
0
    def _broadcast_and_save(self, msg: message.Submit, swap: Swap, swap_id: str):
        try:
            tx_hash = self._broadcast_transaction(msg)
            swap.dst_tx_hash = tx_hash

            swap.status = Status.SWAP_SUBMITTED
            self.pending_txs.append(swap_id)
        except (ValueError, TransactionNotFound) as e:
            self.logger.critical(f"Failed to broadcast transaction for msg {repr(msg)}: {e}")
        finally:
            try:
                swap.save()
            except (DuplicateKeyError, NotUniqueError):
                pass
Пример #8
0
    def run(self):
        """Scans the db for unsigned swap tx and signs them"""
        self.logger.info("Starting..")
        while not self.stop_event.is_set():
            failed = False
            for tx in Swap.objects(status=Status.SWAP_UNSIGNED):

                # if there are 2 transactions that depend on each other (sequence number), and the first fails we mark
                # the next as "retry"
                if failed:
                    tx.status = Status.SWAP_RETRY
                    continue

                self.logger.info(f"Found new unsigned swap event {tx}")
                try:
                    self._validate_and_sign(tx)
                    self.logger.info(
                        f"Signed transaction successfully id:{tx.id}")
                except ValueError as e:
                    self.logger.error(
                        f'Failed to sign transaction: {tx} error: {e}')
                    failed = True

            for tx in Commands.objects(status=Status.SWAP_UNSIGNED):

                self.logger.info(f"Found new unsigned swap event {tx}")
                try:
                    self._sign_add_token(sender="", document=tx)
                    self.logger.info(
                        f"Signed transaction successfully id:{tx.id}")
                except ValueError as e:
                    self.logger.error(
                        f'Failed to sign transaction: {tx} error: {e}')

            self.stop_event.wait(self.config.sleep_interval)
Пример #9
0
def test_3_confirm_tx(web3_provider, ethr_signers, configuration: Config,
                      erc20_contract, ethr_leader):

    secret_token_addr = TokenPairing.objects().get(src_network="Ethereum",
                                                   src_coin="ERC").dst_address

    assert increase_block_number(web3_provider,
                                 configuration.eth_confirmations)
    # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum
    # ethr_signers[-1].start()

    sleep(configuration.sleep_interval + 3)
    # Validate the tx is confirmed in the smart contract
    last_nonce = SwapTrackerObject.last_processed(src=secret_token_addr)
    assert last_nonce > -1
    # account for fee

    fee = erc20_contract.contract.functions.balanceOf(PAYABLE_ADDRESS).call()
    assert fee > 0
    assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf(
        ethr_leader.signer.address).call() + fee

    assert increase_block_number(web3_provider,
                                 configuration.eth_confirmations)
    sleep(configuration.sleep_interval)

    last_nonce = SwapTrackerObject.last_processed(secret_token_addr)
    swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}')
    assert swap.status == Status.SWAP_CONFIRMED
Пример #10
0
def test_3_confirm_and_finalize_eth_tx(web3_provider, ethr_signers,
                                       configuration: Config):
    # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum
    secret_token_addr = TokenPairing.objects().get(src_network="Ethereum",
                                                   src_coin="ETH").dst_address
    prev_bal = web3_provider.eth.getBalance(zero_address, "latest")
    prev_bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest")
    ethr_signers[-1].start()
    sleep(1)
    assert increase_block_number(web3_provider,
                                 configuration.eth_confirmations)

    sleep(configuration.sleep_interval * 5)
    # Validate the tx is confirmed in the smart contract
    last_nonce = SwapTrackerObject.last_processed(secret_token_addr)
    # ethr_signers[-1].signer.multisig_contract.contract.functions.confirmations(last_nonce,
    #                                                                            ethr_signers[-1].account).call()

    assert last_nonce >= 0

    bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest")
    assert bal_fee > prev_bal_fee

    bal = web3_provider.eth.getBalance(zero_address, "latest")
    assert bal > prev_bal

    last_nonce = SwapTrackerObject.last_processed(secret_token_addr)

    swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}')
    assert swap.status == Status.SWAP_CONFIRMED
    configuration.eth_start_block = web3_provider.eth.blockNumber
Пример #11
0
    def _handle_swap(self, swap_data: str, src_token: str, dst_token: str, retry=False):
        swap_json = swap_query_res(swap_data)
        # this is an id, and not the TX hash since we don't actually know where the TX happened, only the id of the
        # swap reported by the contract
        swap_id = get_swap_id(swap_json)
        dest_address = swap_json['destination']
        self.logger.debug(f'{swap_json}')
        amount = int(swap_json['amount'])

        swap_failed = False
        fee = 0
        data = b''
        nonce = int(swap_json['nonce'])
        swap = None

        try:
            if dst_token == 'native':
                data, tx_dest, tx_amount, tx_token, fee = self._tx_native_params(amount, dest_address, retry)
            else:
                self.erc20.address = dst_token
                data, tx_dest, tx_amount, tx_token, fee = self._tx_erc20_params(amount, dest_address, dst_token, retry)

            if retry:

                original_nonce = nonce
                nonce = int(self.multisig_wallet.get_token_nonce(swap_retry_address) + 1)  # + 1 to advance the counter

                swap = Swap.objects.get(src_tx_hash=swap_id)
                swap.status = Status.SWAP_FAILED

                self.update_retry_db(f"{original_nonce}|{tx_token}", f"{nonce}|{swap_retry_address.lower()}")

                tx_token = w3.toChecksumAddress(swap_retry_address)

            msg = message.Submit(w3.toChecksumAddress(tx_dest),
                                 tx_amount,  # if we are swapping token, no ether should be rewarded
                                 nonce,
                                 tx_token,
                                 fee,
                                 data)

        except ValueError as e:
            self.logger.error(f"Error: {e}")
            swap_failed = True

        # this could have already been set by retry
        if not swap:
            swap = Swap(src_network="Secret", src_tx_hash=swap_id, unsigned_tx=data, src_coin=src_token,
                        dst_coin=dst_token, dst_address=dest_address, amount=str(amount), dst_network="Ethereum",
                        status=Status.SWAP_FAILED)

        if swap_failed or not self._validate_fee(amount, fee):
            self._save_failed_swap(swap, swap_id)
        else:
            self._broadcast_and_save(msg, swap, swap_id)
Пример #12
0
    def _validate_and_sign(self, tx: Swap):
        """
        Makes sure that the tx is valid and signs it

        :raises: ValueError
        """
        if self._is_signed(tx):
            self.logger.debug(
                f"This signer already signed this transaction. Waiting for other signers... id:{tx.id}"
            )
            return

        if not self._is_valid(tx):
            self.logger.error(
                f"Validation failed. Signer: {self.multisig.name}. Tx id:{tx.id}."
            )
            tx.status = Status.SWAP_FAILED
            tx.save()
            raise ValueError

        try:
            signed_tx = self._sign_with_secret_cli(tx.unsigned_tx, tx.sequence)
        except RuntimeError as e:
            tx.status = Status.SWAP_FAILED
            tx.save()
            raise ValueError from e

        try:
            Signatures(tx_id=tx.id,
                       signer=self.multisig.name,
                       signed_tx=signed_tx).save()
        except OperationError as e:
            self.logger.error(f'Failed to save tx in database: {tx}')
            raise ValueError from e
Пример #13
0
    def _broadcast_validation(self, document: Swap):  # pylint: disable=unused-argument
        """validation of submitted broadcast signed tx

        **kwargs needs to be here even if unused, because this function gets passed arguments from mongo internals
        """
        if not document.status == Status.SWAP_SUBMITTED:
            return

        tx_hash = document.dst_tx_hash
        try:
            res = query_data_success(tx_hash)

            if res and res["mint_from_ext_chain"]["status"] == "success":
                document.update(status=Status.SWAP_CONFIRMED)
            else:
                # maybe the block took a long time - we wait 60 seconds before we mark it as failed
                if (datetime.utcnow() - document.updated_on
                    ).total_seconds() < BROADCAST_VALIDATION_COOLDOWN:
                    return
                document.update(status=Status.SWAP_FAILED)
                self.logger.critical(
                    f"Failed confirming broadcast for tx: {document}")
        except (IndexError, json.JSONDecodeError, RuntimeError) as e:
            self.logger.critical(
                f"Failed confirming broadcast for tx: {document}. Error: {e}")
            # This can fail, but if it does we want to crash - this can lead to duplicate amounts and confusion
            # Better to just stop and make sure everything is kosher before continuing
            document.update(status=Status.SWAP_FAILED)
Пример #14
0
    def run(self):
        """Scans for signed transactions and updates status if multisig threshold achieved"""
        self.logger.info("Starting..")

        to_block = w3.eth.blockNumber - self.config.eth_confirmations

        self.catch_up(to_block)

        self.event_listener.start()
        self.logger.info("Done catching up")

        while not self.stop_signal.is_set():
            for transaction in Swap.objects(status=Status.SWAP_RETRY,
                                            src_network="Ethereum"):
                self._retry(transaction)

            for transaction in Swap.objects(status=Status.SWAP_UNSIGNED):
                self.handle_unsigned_tx(transaction)

            for transaction in Commands.objects(status=Status.SWAP_UNSIGNED):
                self.handle_unsigned_tx(transaction)

            self.stop_signal.wait(self.config.sleep_interval)
Пример #15
0
    def _scan_swap(self):
        """ Scans secret network contract for swap events """
        self.logger.info(f'Starting for account {self.signer.address} with tokens: {self.token_map=}')
        while not self.stop_event.is_set():

            num_of_tokens = TokenPairing.objects(src_network=self.network).count()
            if num_of_tokens != len(self.token_map.keys()):
                self._refresh_token_map()
                self.logger.info(f'Refreshed tracked tokens. Now tracking {len(self.token_map.keys())} tokens')

            for transaction in Swap.objects(status=Status.SWAP_RETRY, src_network="Secret"):
                # self._handle_swap(swap_data, token, self.token_map[token].address)
                try:
                    token, nonce = _parse_db_tx(transaction)
                    swap_data = query_scrt_swap(nonce, self.config.scrt_swap_address, token)
                    # self._retry(transaction)
                    self._handle_swap(swap_data, token, self.token_map[token].address, True)
                except Exception as e:  # pylint: disable=broad-except
                    self.logger.error(f'Failed to retry swap: {e}')
                    transaction.update(status=Status.SWAP_FAILED)

            for token in self.token_map:
                try:
                    swap_tracker = SwapTrackerObject.get_or_create(src=token)
                    next_nonce = swap_tracker.nonce + 1

                    self.logger.debug(f'Scanning token {token} for query #{next_nonce}')

                    swap_data = query_scrt_swap(next_nonce, self.config.scrt_swap_address, token)

                    self._handle_swap(swap_data, token, self.token_map[token].address)
                    swap_tracker.nonce = next_nonce
                    swap_tracker.save()
                    next_nonce += 1

                except CalledProcessError as e:
                    if b'ERROR: query result: encrypted: Failed to get swap for token' not in e.stderr:
                        self.logger.error(f"Failed to query swap: stdout: {e.stdout} stderr: {e.stderr}")
                        # if b'ERROR: query result: encrypted: Failed to get swap for key' not in e.stderr:

            self.stop_event.wait(self.config.sleep_interval)
Пример #16
0
    def _broadcast_validation(self, document: Swap) -> bool:  # pylint: disable=unused-argument
        """validation of submitted broadcast signed tx

        **kwargs needs to be here even if unused, because this function gets passed arguments from mongo internals
        """
        if not document.status == Status.SWAP_SUBMITTED or not document.src_network == "Ethereum":
            return False

        tx_hash = document.dst_tx_hash
        try:
            res = query_data_success(tx_hash)

            if res and res["mint_from_ext_chain"]["status"] == "success":
                self.logger.info("Updated status to confirmed")
                document.update(status=Status.SWAP_CONFIRMED)
                return True

            # maybe the block took a long time - we wait 60 seconds before we mark it as failed
            # The returned value is just here to let us know if we need to retry the next transactions
            if (datetime.utcnow() - document.updated_on
                ).total_seconds() < BROADCAST_VALIDATION_COOLDOWN:
                return True

            # TX isn't on-chain. We can retry it
            document.update(status=Status.SWAP_RETRY)

            # update sequence number - just in case we failed because we are out of sync
            self.manager.update_sequence()
            self.logger.critical(
                f"Failed confirming broadcast for tx: {repr(document)}, Hash: {tx_hash}, res: {res}"
            )
            return False
        except (ValueError, KeyError) as e:
            # TX failed for whatever reason. Might be a duplicate, out of gas, or any other reason
            self.logger.error(
                f"Failed confirming broadcast for tx: {repr(document)}. Error: {e}"
            )
            # The DB update can fail, but if it does we want to crash - this can lead to
            # duplicate amounts and confusion. Better to just stop and make sure
            # everything is kosher before continuing
            document.update(status=Status.SWAP_FAILED)
            self.manager.update_sequence()
            return False
Пример #17
0
 def _catch_up(self):
     """ Scans the DB for signed swap tx at startup """
     # Note: As Collection.objects() call is cached, there shouldn't be collisions with DB signals
     for tx in Swap.objects(status=Status.SWAP_SIGNED).order_by('sequence'):
         self._create_and_broadcast(tx)
Пример #18
0
def _set_retry(tx: Swap):
    tx.status = Status.SWAP_RETRY
    tx.save()
Пример #19
0
 def get_swap(nonce, token):
     return Swap.objects().get(src_tx_hash=build_hash(nonce, token))
Пример #20
0
def test_11_swap_erc_to_s20(scrt_leader, scrt_signers, web3_provider,
                            configuration: Config, erc20_contract,
                            multisig_wallet, ethr_leader):

    secret_token_addr = TokenPairing.objects().get(src_network="Ethereum",
                                                   src_coin="ERC").dst_address

    # scrt_leader.start()
    # for signer in scrt_signers:
    #     signer.start()

    t1_address = get_key_signer(
        "t1", Path.joinpath(project_base_path(),
                            configuration.path_to_keys))['address']
    # swap ethr for secret20 token, deliver tokens to address of 'a'.
    # (we will use 'a' later to check it received the money)

    # add usdt to the whitelisted token list
    account = web3_provider.eth.account.from_key(configuration.leader_key)
    nonce = web3_provider.eth.getTransactionCount(account.address, "pending")
    tx = multisig_wallet.contract.functions.addToken(erc20_contract.address, 1)
    raw_tx = tx.buildTransaction(transaction={
        'from': account.address,
        'gas': 3000000,
        'nonce': nonce
    })
    signed_tx = account.sign_transaction(raw_tx)
    tx_hash = web3_provider.eth.sendRawTransaction(signed_tx.rawTransaction)

    # Get transaction hash from deployed contract
    tx_receipt = web3_provider.eth.waitForTransactionReceipt(tx_hash)

    # this will likely fail since the test before also allocates the allowance - just ignore if it fails
    try:
        _ = erc20_contract.contract.functions.approve(multisig_wallet.address, TRANSFER_AMOUNT_ERC). \
            transact({'from': web3_provider.eth.coinbase})
    except ValueError:
        pass

    tx_hash = multisig_wallet\
        .contract\
        .functions\
        .swapToken(t1_address.encode(), TRANSFER_AMOUNT_ERC, erc20_contract.address)\
        .transact({'from': web3_provider.eth.coinbase}).hex().lower()

    assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf(
        multisig_wallet.address).call()

    # increase number of blocks to reach the confirmation threshold
    assert increase_block_number(web3_provider,
                                 configuration.eth_confirmations - 1)

    sleep(configuration.sleep_interval + 2)

    assert Swap.objects(src_tx_hash=tx_hash).count(
    ) == 0  # verify blocks confirmation threshold wasn't meet
    assert increase_block_number(web3_provider,
                                 1)  # add the 'missing' confirmation block

    # give event listener and manager time to process tx
    sleep(configuration.sleep_interval + 2)
    assert Swap.objects(
        src_tx_hash=tx_hash).count() == 1  # verify swap event recorded

    swap = Swap.objects(src_tx_hash=tx_hash).get()

    sleep(1)
    # check signers were notified of the tx and signed it
    assert Signatures.objects(tx_id=swap.id).count() == len(scrt_signers)

    # give time for manager to process the signatures
    sleep(configuration.sleep_interval + 2)
    assert Swap.objects().get(
        src_tx_hash=tx_hash).status in (Status.SWAP_SUBMITTED,
                                        Status.SWAP_CONFIRMED)

    _, log = event_log(tx_hash, ['SwapToken'], web3_provider,
                       multisig_wallet.contract)
    transfer_amount = multisig_wallet.extract_amount(log)
    dest = multisig_wallet.extract_addr(log)

    # validate swap tx on ethr delivers to the destination
    viewing_key_set = '{"set_viewing_key": {"key": "lol"}}'
    _ = run(
        f"secretcli tx compute execute {secret_token_addr} "
        f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'",
        shell=True,
        stdout=PIPE)
    sleep(6)

    balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}'
    res = run(f"secretcli q compute query {secret_token_addr} "
              f"'{balance}'",
              shell=True,
              stdout=PIPE)

    print(f"{res.stdout=}")

    amount = json.loads(res.stdout)["balance"]["amount"]

    print(f"swap amount: {transfer_amount}, dest balance amount: {amount}")

    # give scrt_leader time to multi-sign already existing signatures
    sleep(configuration.sleep_interval + 5)
    assert Swap.objects().get(
        src_tx_hash=tx_hash).status == Status.SWAP_CONFIRMED
Пример #21
0
def test_1_swap_eth_to_s20(setup, scrt_signers, scrt_leader, web3_provider,
                           configuration: Config, multisig_wallet):

    secret_token_addr = TokenPairing.objects().get(src_network="Ethereum",
                                                   src_coin="ETH").dst_address

    scrt_leader.start()
    for signer in scrt_signers:
        signer.start()

    fee_collector = multisig_wallet.contract.functions.getFeeCollector().call()

    print(f"{fee_collector=}")

    t1_address = get_key_signer(
        "t1", Path.joinpath(project_base_path(),
                            configuration.path_to_keys))['address']
    # swap ethr for secret20 token, deliver tokens to address of 'a'
    # (we will use 'a' later to check it received the money)
    print(
        f"Creating new swap transaction at {web3_provider.eth.getBlock('latest').number + 1}"
    )
    tx_hash = multisig_wallet.contract.functions.swap(t1_address.encode()). \
        transact({'from': web3_provider.eth.coinbase, 'value': TRANSFER_AMOUNT_ETH}).hex().lower()
    # TODO: validate ethr increase of the smart contract
    # increase number of blocks to reach the confirmation threshold
    assert increase_block_number(web3_provider,
                                 configuration.eth_confirmations - 1)

    sleep(configuration.sleep_interval + 2)

    assert Swap.objects(src_tx_hash=tx_hash).count(
    ) == 0  # verify blocks confirmation threshold wasn't meet
    assert increase_block_number(web3_provider,
                                 1)  # add the 'missing' confirmation block

    # give event listener and manager time to process tx
    sleep(configuration.sleep_interval + 5)
    assert Swap.objects(
        src_tx_hash=tx_hash).count() == 1  # verify swap event recorded

    sleep(1)
    # check signers were notified of the tx and signed it
    assert Signatures.objects().count() == len(scrt_signers)

    sleep(5)

    if Swap.objects().get().status == Status.SWAP_SUBMITTED:
        sleep(configuration.sleep_interval + 5)
    assert Swap.objects().get().status == Status.SWAP_CONFIRMED

    # get tx details
    tx_hash = Swap.objects().get().src_tx_hash
    _, log = event_log(tx_hash, ['Swap'], web3_provider,
                       multisig_wallet.contract)
    transfer_amount = log.args.amount
    dest = log.args.recipient.decode()

    # validate swap tx on ethr delivers to the destination
    viewing_key_set = '{"set_viewing_key": {"key": "lol"}}'
    tx_hash = run(
        f"secretcli tx compute execute {secret_token_addr} "
        f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'",
        shell=True,
        stdout=PIPE)
    sleep(6)

    balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}'
    res = run(f"secretcli q compute query {secret_token_addr} "
              f"'{balance}'",
              shell=True,
              stdout=PIPE)

    print(f"{res.stdout=}")

    amount = json.loads(res.stdout)["balance"]["amount"]

    print(f"swap amount: {transfer_amount}, dest balance amount: {amount}")

    assert int(amount) == log.args.amount
Пример #22
0
def test_1_swap_erc_to_s20(setup, scrt_leader, scrt_signers, web3_provider,
                           configuration: Config, erc20_contract,
                           multisig_wallet):

    scrt_leader.start()
    for signer in scrt_signers:
        signer.start()

    t1_address = get_key_signer(
        "t1", Path.joinpath(project_base_path(),
                            configuration['path_to_keys']))['address']
    # swap ethr for secret20 token, deliver tokens to address of 'a'.
    # (we will use 'a' later to check it received the money)
    tx_hash = erc20_contract.contract.functions.transfer(multisig_wallet.address,
                                                         TRANSFER_AMOUNT,
                                                         t1_address.encode()). \
        transact({'from': web3_provider.eth.coinbase}).hex().lower()
    assert TRANSFER_AMOUNT == erc20_contract.contract.functions.balanceOf(
        multisig_wallet.address).call()

    # increase number of blocks to reach the confirmation threshold
    assert increase_block_number(web3_provider,
                                 configuration['eth_confirmations'] - 1)

    sleep(configuration['sleep_interval'] + 2)

    assert Swap.objects(src_tx_hash=tx_hash).count(
    ) == 0  # verify blocks confirmation threshold wasn't meet
    assert increase_block_number(web3_provider,
                                 1)  # add the 'missing' confirmation block

    # give event listener and manager time to process tx
    sleep(configuration['sleep_interval'] + 2)
    assert Swap.objects(
        src_tx_hash=tx_hash).count() == 1  # verify swap event recorded

    sleep(1)
    # check signers were notified of the tx and signed it
    assert Signatures.objects().count() == len(scrt_signers)

    # give time for manager to process the signatures
    sleep(configuration['sleep_interval'] + 2)
    assert Swap.objects().get().status == Status.SWAP_SUBMITTED

    # get tx details
    tx_hash = Swap.objects().get().src_tx_hash
    _, log = event_log(tx_hash, ['Transfer'], web3_provider,
                       erc20_contract.contract)
    transfer_amount = erc20_contract.extract_amount(log)
    dest = erc20_contract.extract_addr(log)

    # validate swap tx on ethr delivers to the destination
    balance_query = '{"balance": {}}'
    tx_hash = run(
        f"secretcli tx compute execute {configuration['secret_contract_address']} "
        f"'{balance_query}' --from {dest} -b block -y | jq '.txhash'",
        shell=True,
        stdout=PIPE)
    tx_hash = tx_hash.stdout.decode().strip()[1:-1]

    res = run(
        f"secretcli q compute tx {tx_hash} | jq '.output_log' | jq '.[0].attributes' "
        f"| jq '.[3].value'",
        shell=True,
        stdout=PIPE).stdout.decode().strip()[1:-1]
    end_index = res.find(' ')
    amount = Decimal(res[:end_index])

    print(f"tx amount: {transfer_amount}, swap amount: {amount}")
    # assert abs(transfer_amount - amount) < 1  ??????????????????????

    # give scrt_leader time to multi-sign already existing signatures
    sleep(configuration['sleep_interval'] + 3)
    assert Swap.objects().get().status == Status.SWAP_CONFIRMED
Пример #23
0
    def _handle_swap(self, swap_data: str, src_token: str, dst_token: str):
        swap_json = swap_query_res(swap_data)
        # this is an id, and not the TX hash since we don't actually know where the TX happened, only the id of the
        # swap reported by the contract
        swap_id = get_swap_id(swap_json)
        dest_address = swap_json['destination']
        self.logger.info(f'{swap_json}')
        amount = int(swap_json['amount'])

        if dst_token == 'native':
            data, tx_dest, tx_amount, tx_token, fee = self._tx_native_params(
                amount, dest_address)
        else:
            self.erc20.address = dst_token
            data, tx_dest, tx_amount, tx_token, fee = self._tx_erc20_params(
                amount, dest_address, dst_token)

        if not self._validate_fee(amount, fee):
            self.logger.error("Tried to swap an amount too low to cover fee")
            swap = Swap(src_network="Secret",
                        src_tx_hash=swap_id,
                        unsigned_tx=data,
                        src_coin=src_token,
                        dst_coin=dst_token,
                        dst_address=dest_address,
                        amount=str(amount),
                        dst_network="Ethereum",
                        status=Status.SWAP_FAILED)
            try:
                swap.save()
            except (DuplicateKeyError, NotUniqueError):
                pass
            return

        msg = message.Submit(
            tx_dest,
            tx_amount,  # if we are swapping token, no ether should be rewarded
            int(swap_json['nonce']),
            tx_token,
            fee,
            data)
        # todo: check we have enough ETH
        swap = Swap(src_network="Secret",
                    src_tx_hash=swap_id,
                    unsigned_tx=data,
                    src_coin=src_token,
                    dst_coin=dst_token,
                    dst_address=dest_address,
                    amount=str(amount),
                    dst_network="Ethereum",
                    status=Status.SWAP_FAILED)
        try:
            tx_hash = self._broadcast_transaction(msg)
            swap.dst_tx_hash = tx_hash
            swap.status = Status.SWAP_SUBMITTED
        except (ValueError, TransactionNotFound) as e:
            self.logger.critical(
                f"Failed to broadcast transaction for msg {repr(msg)}: {e}")
        finally:
            try:
                swap.save()
            except (DuplicateKeyError, NotUniqueError):
                pass