def _build_proxy_contract_creation_constructor(
            self, master_copy: str, initializer: bytes, funder: str,
            payment_token: str, payment: int) -> ContractConstructor:
        """
        :param master_copy: Master Copy of Gnosis Safe already deployed
        :param initializer: Data initializer to send to GnosisSafe setup method
        :param funder: Address that should get the payment (if payment set)
        :param payment_token: Address if a token is used. If not set, 0x0 will be ether
        :param payment: Payment
        :return: Transaction dictionary
        """
        if not funder or funder == NULL_ADDRESS:
            funder = NULL_ADDRESS
            payment = 0

        return get_paying_proxy_contract(self.w3).constructor(
            master_copy, initializer, funder, payment_token, payment)
Beispiel #2
0
    def test_create_multisig_tx(self):
        w3 = self.w3

        # The balance we will send to the safe
        safe_balance = w3.toWei(0.02, 'ether')

        # Create Safe
        funder_account = self.ethereum_test_account
        funder = funder_account.address
        accounts = [self.create_account(), self.create_account()]
        # Signatures must be sorted!
        accounts.sort(key=lambda account: account.address.lower())
        owners = [x.address for x in accounts]
        threshold = len(accounts)

        safe_creation = self.deploy_test_safe(owners=owners,
                                              threshold=threshold)
        my_safe_address = safe_creation.safe_address
        my_safe_contract = get_safe_contract(w3, my_safe_address)
        SafeContractFactory(address=my_safe_address)

        to = funder
        value = safe_balance // 4
        data = HexBytes('')
        operation = 0
        safe_tx_gas = 100000
        data_gas = 300000
        gas_price = self.transaction_service._get_minimum_gas_price()
        gas_token = NULL_ADDRESS
        refund_receiver = NULL_ADDRESS
        safe = Safe(my_safe_address, self.ethereum_client)
        nonce = safe.retrieve_nonce()
        safe_tx = safe.build_multisig_tx(to,
                                         value,
                                         data,
                                         operation,
                                         safe_tx_gas,
                                         data_gas,
                                         gas_price,
                                         gas_token,
                                         refund_receiver,
                                         safe_nonce=nonce).safe_tx_hash

        # Just to make sure we are not miscalculating tx_hash
        contract_multisig_tx_hash = my_safe_contract.functions.getTransactionHash(
            to, value, data, operation, safe_tx_gas, data_gas, gas_price,
            gas_token, refund_receiver, nonce).call()

        self.assertEqual(safe_tx, contract_multisig_tx_hash)

        signatures = [account.signHash(safe_tx) for account in accounts]

        # Check owners are the same
        contract_owners = my_safe_contract.functions.getOwners().call()
        self.assertEqual(set(contract_owners), set(owners))

        invalid_proxy = self.deploy_example_erc20(1, Account.create().address)
        with self.assertRaises(InvalidProxyContract):
            SafeContractFactory(address=invalid_proxy.address)
            self.transaction_service.create_multisig_tx(
                invalid_proxy.address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        # Use invalid master copy
        random_master_copy = Account.create().address
        proxy_create_tx = get_paying_proxy_contract(self.w3).constructor(
            random_master_copy, b'', NULL_ADDRESS, NULL_ADDRESS,
            0).buildTransaction({'from': self.ethereum_test_account.address})
        tx_hash = self.ethereum_client.send_unsigned_transaction(
            proxy_create_tx, private_key=self.ethereum_test_account.key)
        tx_receipt = self.ethereum_client.get_transaction_receipt(tx_hash,
                                                                  timeout=60)
        proxy_address = tx_receipt.contractAddress
        with self.assertRaises(InvalidMasterCopyAddress):
            SafeContractFactory(address=proxy_address)
            self.transaction_service.create_multisig_tx(
                proxy_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        with self.assertRaises(NotEnoughFundsForMultisigTx):
            self.transaction_service.create_multisig_tx(
                my_safe_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        # Send something to the safe
        self.send_tx({
            'to': my_safe_address,
            'value': safe_balance
        }, funder_account)

        bad_refund_receiver = get_eth_address_with_key()[0]
        with self.assertRaises(InvalidRefundReceiver):
            self.transaction_service.create_multisig_tx(
                my_safe_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                bad_refund_receiver,
                nonce,
                signatures,
            )

        invalid_gas_price = 0
        with self.assertRaises(RefundMustBeEnabled):
            self.transaction_service.create_multisig_tx(
                my_safe_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                invalid_gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        with self.assertRaises(GasPriceTooLow):
            self.transaction_service.create_multisig_tx(
                my_safe_address, to, value, data, operation, safe_tx_gas,
                data_gas,
                self.transaction_service._estimate_tx_gas_price(
                    self.transaction_service._get_minimum_gas_price(),
                    gas_token) - 1, gas_token, refund_receiver, nonce,
                signatures)

        with self.assertRaises(InvalidGasToken):
            invalid_gas_token = Account.create().address
            self.transaction_service.create_multisig_tx(
                my_safe_address, to, value, data, operation, safe_tx_gas,
                data_gas, gas_price, invalid_gas_token, refund_receiver, nonce,
                reversed(signatures))

        with self.assertRaises(SignaturesNotSorted):
            self.transaction_service.create_multisig_tx(
                my_safe_address, to, value, data, operation, safe_tx_gas,
                data_gas, gas_price, gas_token, refund_receiver, nonce,
                reversed(signatures))

        with self.assertRaises(SignerIsBanned):
            for account in accounts:
                BannedSignerFactory(address=account.address)
            self.transaction_service.create_multisig_tx(
                my_safe_address, to, value, data, operation, safe_tx_gas,
                data_gas, gas_price, gas_token, refund_receiver, nonce,
                signatures)
        BannedSigner.objects.all().delete()
        self.assertEqual(BannedSigner.objects.count(), 0)

        sender = self.transaction_service.tx_sender_account.address
        sender_balance = w3.eth.getBalance(sender)
        safe_balance = w3.eth.getBalance(my_safe_address)

        safe_multisig_tx = self.transaction_service.create_multisig_tx(
            my_safe_address,
            to,
            value,
            data,
            operation,
            safe_tx_gas,
            data_gas,
            gas_price,
            gas_token,
            refund_receiver,
            nonce,
            signatures,
        )

        with self.assertRaises(SafeMultisigTxExists):
            self.transaction_service.create_multisig_tx(
                my_safe_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        tx_receipt = w3.eth.waitForTransactionReceipt(
            safe_multisig_tx.ethereum_tx.tx_hash)
        self.assertTrue(tx_receipt['status'])
        self.assertEqual(w3.toChecksumAddress(tx_receipt['from']), sender)
        self.assertEqual(w3.toChecksumAddress(tx_receipt['to']),
                         my_safe_address)
        self.assertGreater(safe_multisig_tx.ethereum_tx.gas_price,
                           gas_price)  # We used minimum gas price

        sender_new_balance = w3.eth.getBalance(sender)
        gas_used = tx_receipt['gasUsed']
        tx_fees = gas_used * safe_multisig_tx.ethereum_tx.gas_price
        estimated_refund = (
            safe_multisig_tx.data_gas +
            safe_multisig_tx.safe_tx_gas) * safe_multisig_tx.gas_price
        real_refund = safe_balance - w3.eth.getBalance(my_safe_address) - value
        # Real refund can be less if not all the `safe_tx_gas` is used
        self.assertGreaterEqual(estimated_refund, real_refund)
        self.assertEqual(sender_new_balance,
                         sender_balance - tx_fees + real_refund)
        self.assertEqual(safe.retrieve_nonce(), 1)

        # Send again the tx and check that works
        nonce += 1
        value = 0
        safe_tx = safe.build_multisig_tx(to,
                                         value,
                                         data,
                                         operation,
                                         safe_tx_gas,
                                         data_gas,
                                         gas_price,
                                         gas_token,
                                         refund_receiver,
                                         safe_nonce=nonce)

        # Use invalid signatures
        with self.assertRaises(InvalidOwners):
            signatures = [
                Account.create().signHash(safe_tx.safe_tx_hash)
                for _ in range(len(accounts))
            ]
            self.transaction_service.create_multisig_tx(
                my_safe_address,
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                data_gas,
                gas_price,
                gas_token,
                refund_receiver,
                nonce,
                signatures,
            )

        signatures = [
            account.signHash(safe_tx.safe_tx_hash) for account in accounts
        ]
        safe_multisig_tx = self.transaction_service.create_multisig_tx(
            my_safe_address,
            to,
            value,
            data,
            operation,
            safe_tx_gas,
            data_gas,
            gas_price,
            gas_token,
            refund_receiver,
            nonce,
            signatures,
        )
        tx_receipt = w3.eth.waitForTransactionReceipt(
            safe_multisig_tx.ethereum_tx.tx_hash)
        self.assertTrue(tx_receipt['status'])