Example #1
0
    def _create_and_broadcast(self, tx: Union[Swap, Commands]) -> bool:
        # 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 False

        try:
            signed_tx = self._create_multisig(tx.unsigned_tx, tx.sequence,
                                              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")
            return True
        except (RuntimeError, OperationError) as e:
            self.logger.error(
                msg=f"Failed to create multisig and broadcast, error: {e}")
            tx.status = Status.SWAP_FAILED
            tx.save()
            return False
Example #2
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
Example #3
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
Example #4
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)
Example #5
0
 def handle_unsigned_tx(self, transaction: Union[Commands, Swap]):
     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")
Example #6
0
 def sign(self, tx: Union[Commands, Swap]):
     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
Example #7
0
 def _is_signed(self, tx: Union[Swap, Commands]) -> bool:
     """ Returns True if tx was already signed by us, else False """
     return Signatures.objects(tx_id=tx.id,
                               signer=self.multisig.name).count() > 0
Example #8
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
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
Example #10
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