Beispiel #1
0
    def test_safe_creation_with_fixed_cost(self):
        s = generate_valid_s()
        owner1, _ = get_eth_address_with_key()
        owner2, _ = get_eth_address_with_key()
        serializer = SafeCreationSerializer(data={
            's': s,
            'owners': [owner1, owner2],
            'threshold': 2
        })
        self.assertTrue(serializer.is_valid())
        fixed_creation_cost = 123
        with self.settings(SAFE_FIXED_CREATION_COST=fixed_creation_cost):
            SafeCreationServiceProvider.del_singleton()
            response = self.client.post(reverse('v1:safe-creation'),
                                        data=serializer.data,
                                        format='json')
            response_json = response.json()
            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
            deployer = response_json['deployer']
            self.assertTrue(check_checksum(deployer))
            self.assertTrue(check_checksum(response_json['safe']))
            self.assertTrue(check_checksum(response_json['funder']))
            self.assertEqual(response_json['paymentToken'], NULL_ADDRESS)
            self.assertEqual(int(response_json['payment']),
                             fixed_creation_cost)

            safe_creation = SafeCreation.objects.get(deployer=deployer)
            self.assertEqual(safe_creation.payment_token, None)
            self.assertEqual(safe_creation.payment, fixed_creation_cost)
            self.assertGreater(safe_creation.wei_deploy_cost(),
                               safe_creation.payment)
            SafeCreationServiceProvider.del_singleton()
Beispiel #2
0
    def test_safe_multisig_tx_estimate_serializer(self):
        safe_address, _ = get_eth_address_with_key()
        eth_address, _ = get_eth_address_with_key()
        data = {
            'safe': safe_address,
            'to': None,
            'data': None,
            'value': 1,
            'operation': 0
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)

        # To and data cannot be empty
        self.assertFalse(serializer.is_valid())

        data = {
            'safe': safe_address,
            'to': eth_address,
            'data': '0x00',
            'value': 1,
            'operation': 2
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation cannot be contract creation and to set
        self.assertFalse(serializer.is_valid())

        data = {
            'safe': safe_address,
            'to': None,
            'data': None,
            'value': 1,
            'operation': 2
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation is not contract creation and to is not empty
        self.assertFalse(serializer.is_valid())

        data = {
            'safe': safe_address,
            'to': eth_address,
            'data': '0x00',
            'value': 1,
            'operation': 0
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation is not contract creation and to is not empty
        self.assertTrue(serializer.is_valid())

        data = {
            'safe': safe_address,
            'to': None,
            'data': '0x00',
            'value': 1,
            'operation': 2
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation is not contract creation and to is not empty
        self.assertTrue(serializer.is_valid())
Beispiel #3
0
    def test_safe_multisig_tx_estimate(self):
        my_safe_address = get_eth_address_with_invalid_checksum()
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)

        my_safe_address, _ = get_eth_address_with_key()
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        initial_funding = self.w3.toWei(0.0001, 'ether')
        to, _ = get_eth_address_with_key()
        data = {
            'to': to,
            'value': initial_funding // 2,
            'data': '0x',
            'operation': 1
        }

        safe_creation = self.deploy_test_safe(
            number_owners=3, threshold=2, initial_funding_wei=initial_funding)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)

        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response = response.json()
        self.assertGreater(response['safeTxGas'], 0)
        self.assertGreater(response['dataGas'], 0)
        self.assertGreater(response['gasPrice'], 0)
        self.assertIsNone(response['lastUsedNonce'])
        self.assertEqual(response['gasToken'], NULL_ADDRESS)

        to, _ = get_eth_address_with_key()
        data = {
            'to': to,
            'value': initial_funding // 2,
            'data': None,
            'operation': 0
        }
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
    def test_safe_creation_tx_builder_with_payment(self):
        logger.info("Test Safe Proxy creation With Payment".center(LOG_TITLE_WIDTH, '-'))
        w3 = self.w3

        s = generate_valid_s()

        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(2)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(w3=w3,
                                          owners=owners,
                                          threshold=threshold,
                                          signature_s=s,
                                          master_copy=self.safe_contract_V0_0_1_address,
                                          gas_price=gas_price,
                                          funder=funder_account.address)

        user_external_account = Account.create()
        # Send some ether to that account
        safe_balance = w3.toWei(0.01, 'ether')
        self.send_tx({
            'to': user_external_account.address,
            'value': safe_balance * 2
        }, funder_account)

        logger.info("Send %d ether to safe %s", w3.fromWei(safe_balance, 'ether'), safe_creation_tx.safe_address)
        self.send_tx({
            'to': safe_creation_tx.safe_address,
            'value': safe_balance
        }, user_external_account)
        self.assertEqual(w3.eth.getBalance(safe_creation_tx.safe_address), safe_balance)

        logger.info("Send %d gwei to deployer %s", w3.fromWei(safe_creation_tx.payment_ether, 'gwei'),
                    safe_creation_tx.deployer_address)
        self.send_tx({
            'to': safe_creation_tx.deployer_address,
            'value': safe_creation_tx.payment_ether
        }, funder_account)

        logger.info("Create proxy contract with address %s", safe_creation_tx.safe_address)

        funder_balance = w3.eth.getBalance(funder_account.address)

        # This tx will create the Safe Proxy and return ether to the funder
        tx_hash = w3.eth.sendRawTransaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

        self.assertEqual(w3.eth.getBalance(funder_account.address),
                         funder_balance + safe_creation_tx.payment)

        logger.info("Deployer account has still %d gwei left (will be lost)",
                    w3.fromWei(w3.eth.getBalance(safe_creation_tx.deployer_address), 'gwei'))

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        self.assertEqual(deployed_safe_proxy_contract.functions.getThreshold().call(), threshold)
        self.assertEqual(deployed_safe_proxy_contract.functions.getOwners().call(), owners)
Beispiel #5
0
    def test_checksum_address_validator(self):
        eth_address, eth_key = get_eth_address_with_key()

        self.assertIsNone(validate_checksumed_address(eth_address))

        self.assertRaises(ValidationError, validate_checksumed_address,
                          eth_address.lower())
    def test_safe_creation_tx_builder_with_not_enough_funds(self):
        w3 = self.w3
        s = generate_valid_s()
        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(4)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(w3=w3,
                                          owners=owners,
                                          threshold=threshold,
                                          signature_s=s,
                                          master_copy=self.safe_contract_V0_0_1_address,
                                          gas_price=gas_price,
                                          funder=NULL_ADDRESS)

        logger.info("Send %d gwei to deployer %s",
                    w3.fromWei(safe_creation_tx.payment_ether - 1, 'gwei'),
                    safe_creation_tx.deployer_address)
        self.send_tx({
            'to': safe_creation_tx.deployer_address,
            'value': safe_creation_tx.payment_ether - 1
        }, funder_account)

        with self.assertRaisesMessage(ValueError, 'enough funds'):
            w3.eth.sendRawTransaction(safe_creation_tx.tx_raw)
Beispiel #7
0
 def estimate_safe_creation(
     ethereum_client: EthereumClient,
     old_master_copy_address: str,
     number_owners: int,
     gas_price: int,
     payment_token: Optional[str],
     payment_receiver: str = NULL_ADDRESS,
     payment_token_eth_value: float = 1.0,
     fixed_creation_cost: Optional[int] = None,
 ) -> SafeCreationEstimate:
     s = 15
     owners = [get_eth_address_with_key()[0] for _ in range(number_owners)]
     threshold = number_owners
     safe_creation_tx = SafeCreationTx(
         w3=ethereum_client.w3,
         owners=owners,
         threshold=threshold,
         signature_s=s,
         master_copy=old_master_copy_address,
         gas_price=gas_price,
         funder=payment_receiver,
         payment_token=payment_token,
         payment_token_eth_value=payment_token_eth_value,
         fixed_creation_cost=fixed_creation_cost,
     )
     return SafeCreationEstimate(
         safe_creation_tx.gas,
         safe_creation_tx.gas_price,
         safe_creation_tx.payment,
         safe_creation_tx.payment_token,
     )
Beispiel #8
0
 def estimate_safe_creation_2(
         ethereum_client: EthereumClient,
         master_copy_address: str,
         proxy_factory_address: str,
         number_owners: int,
         gas_price: int,
         payment_token: Optional[str],
         payment_receiver: str = NULL_ADDRESS,
         fallback_handler: Optional[str] = NULL_ADDRESS,
         payment_token_eth_value: float = 1.0,
         fixed_creation_cost: Optional[int] = None) -> SafeCreationEstimate:
     salt_nonce = 15
     owners = [get_eth_address_with_key()[0] for _ in range(number_owners)]
     threshold = number_owners
     safe_creation_tx = SafeCreate2TxBuilder(
         w3=ethereum_client.w3,
         master_copy_address=master_copy_address,
         proxy_factory_address=proxy_factory_address).build(
             owners=owners,
             threshold=threshold,
             fallback_handler=fallback_handler,
             salt_nonce=salt_nonce,
             gas_price=gas_price,
             payment_receiver=payment_receiver,
             payment_token=payment_token,
             payment_token_eth_value=payment_token_eth_value,
             fixed_creation_cost=fixed_creation_cost)
     return SafeCreationEstimate(safe_creation_tx.gas,
                                 safe_creation_tx.gas_price,
                                 safe_creation_tx.payment,
                                 safe_creation_tx.payment_token)
    def test_safe_gas_with_multiple_owners(self):
        logger.info("Test Safe Proxy creation gas with multiple owners".center(LOG_TITLE_WIDTH, '-'))
        w3 = self.w3
        funder_account = self.ethereum_test_account
        number_of_accounts = 10
        for i in range(2, number_of_accounts):
            s = generate_valid_s()
            owners = [get_eth_address_with_key()[0] for _ in range(i + 1)]
            threshold = len(owners)
            gas_price = w3.toWei(15, 'gwei')

            safe_creation_tx = SafeCreationTx(w3=w3,
                                              owners=owners,
                                              threshold=threshold,
                                              signature_s=s,
                                              master_copy=self.safe_contract_V0_0_1_address,
                                              gas_price=gas_price,
                                              funder=None)

            self.send_tx({
                'to': safe_creation_tx.deployer_address,
                'value': safe_creation_tx.payment
            }, funder_account)
            tx_hash = w3.eth.sendRawTransaction(safe_creation_tx.tx_raw)
            tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
            self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

            logger.info("Number of owners: %d - Gas estimated %d - Gas Used %d - Difference %d",
                        len(owners),
                        safe_creation_tx.gas,
                        tx_receipt.gasUsed,
                        safe_creation_tx.gas - tx_receipt.gasUsed)
Beispiel #10
0
    def test_safe_view(self):
        owners_with_keys = [
            get_eth_address_with_key(),
            get_eth_address_with_key(),
            get_eth_address_with_key()
        ]
        owners = [x[0] for x in owners_with_keys]
        threshold = len(owners) - 1
        safe_creation = self.deploy_test_safe(owners=owners,
                                              threshold=threshold)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)
        SafeFundingFactory(
            safe=SafeContract.objects.get(address=my_safe_address),
            safe_deployed=True)
        response = self.client.get(reverse('v1:safe',
                                           args=(my_safe_address, )),
                                   format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        safe_json = response.json()
        self.assertEqual(safe_json['address'], my_safe_address)
        self.assertEqual(safe_json['masterCopy'], self.safe_contract_address)
        self.assertEqual(safe_json['nonce'], 0)
        self.assertEqual(safe_json['threshold'], threshold)
        self.assertEqual(safe_json['owners'], owners)
        self.assertIn('version', safe_json)

        random_address, _ = get_eth_address_with_key()
        response = self.client.get(reverse('v1:safe', args=(random_address, )),
                                   format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        response = self.client.get(reverse('v1:safe',
                                           args=(my_safe_address + ' ', )),
                                   format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)

        response = self.client.get(reverse('v1:safe', args=('0xabfG', )),
                                   format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)

        response = self.client.get(reverse('v1:safe', args=('batman', )),
                                   format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
    def test_funding_serializer(self):
        owner1, _ = get_eth_address_with_key()
        safe_contract = SafeContract.objects.create(address=owner1, master_copy='0x' + '0' * 40)
        safe_funding = SafeFunding.objects.create(safe=safe_contract)

        s = SafeFundingResponseSerializer(safe_funding)

        self.assertTrue(s.data)
    def test_safe_creation_tx_builder(self):
        logger.info(
            "Test Safe Proxy creation without payment".center(LOG_TITLE_WIDTH, "-")
        )
        w3 = self.w3

        s = generate_valid_s()

        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(4)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            funder=NULL_ADDRESS,
        )

        logger.info(
            "Send %d gwei to deployer %s",
            w3.fromWei(safe_creation_tx.payment_ether, "gwei"),
            safe_creation_tx.deployer_address,
        )

        self.send_tx(
            {
                "to": safe_creation_tx.deployer_address,
                "value": safe_creation_tx.payment_ether,
            },
            funder_account,
        )

        logger.info(
            "Create proxy contract with address %s", safe_creation_tx.safe_address
        )

        tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        logger.info(
            "Deployer account has still %d gwei left (will be lost)",
            w3.fromWei(w3.eth.get_balance(safe_creation_tx.deployer_address), "gwei"),
        )

        self.assertEqual(
            deployed_safe_proxy_contract.functions.getThreshold().call(), threshold
        )
        self.assertEqual(
            deployed_safe_proxy_contract.functions.getOwners().call(), owners
        )
    def test_safe_multisig_tx_estimates(self):
        my_safe_address = get_eth_address_with_invalid_checksum()
        response = self.client.post(reverse('v1:safe-multisig-tx-estimates', args=(my_safe_address,)),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)

        my_safe_address, _ = get_eth_address_with_key()
        response = self.client.post(reverse('v1:safe-multisig-tx-estimates', args=(my_safe_address,)),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        initial_funding = self.w3.toWei(0.0001, 'ether')

        safe_creation = self.deploy_test_safe(number_owners=3, threshold=2, initial_funding_wei=initial_funding)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)

        to = Account.create().address
        tx = {
            'to': to,
            'value': initial_funding // 2,
            'data': '0x',
            'operation': 1
        }
        response = self.client.post(reverse('v1:safe-multisig-tx-estimates', args=(my_safe_address,)),
                                    data=tx,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response = response.json()
        self.assertGreater(int(response['safeTxGas']), 0)
        self.assertEqual(response['operationalGas'], '0')
        self.assertIsNone(response['lastUsedNonce'])
        self.assertEqual(len(response['estimations']), 1)
        estimation = response['estimations'][0]
        self.assertGreater(int(estimation['baseGas']), 0)
        self.assertGreater(int(estimation['gasPrice']), 0)
        self.assertEqual(estimation['gasToken'], NULL_ADDRESS)

        valid_token = TokenFactory(address=Account.create().address, gas=True, fixed_eth_conversion=2)
        response = self.client.post(reverse('v1:safe-multisig-tx-estimates', args=(my_safe_address,)),
                                    data=tx,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response = response.json()
        self.assertGreater(int(response['safeTxGas']), 0)
        self.assertEqual(response['operationalGas'], '0')
        self.assertIsNone(response['lastUsedNonce'])
        self.assertEqual(len(response['estimations']), 2)
        estimation_ether = response['estimations'][0]
        self.assertGreater(int(estimation_ether['baseGas']), 0)
        self.assertGreater(int(estimation_ether['gasPrice']), 0)
        self.assertEqual(estimation_ether['gasToken'], NULL_ADDRESS)
        estimation_token = response['estimations'][1]
        self.assertGreater(int(estimation_token['baseGas']), int(estimation_ether['baseGas']))
        self.assertAlmostEqual(int(estimation_token['gasPrice']), int(estimation_ether['gasPrice']) // 2, delta=1.0)
        self.assertEqual(estimation_token['gasToken'], valid_token.address)
    def test_estimate_safe_creation2(self):
        gas_price = self.safe_creation_service._get_configured_gas_price()

        number_owners = 4
        payment_token = None
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, 0)
        self.assertEqual(safe_creation_estimate.payment_token, NULL_ADDRESS)
        estimated_payment = safe_creation_estimate.payment

        # Compare with safe_creation
        salt_nonce = 4815162342
        owners = [Account.create().address for _ in range(number_owners)]
        threshold = 1
        safe_creation_2 = self.safe_creation_service.create2_safe_tx(
            salt_nonce, owners, threshold, payment_token=payment_token)
        self.assertAlmostEqual(safe_creation_2.gas_estimated,
                               safe_creation_estimate.gas,
                               delta=1000)
        # Compare with estimation for all tokens (position 0 is ether)
        self.assertAlmostEqual(
            self.safe_creation_service.estimate_safe_creation_for_all_tokens(
                number_owners)[0].gas,
            safe_creation_estimate.gas,
            delta=1000,
        )

        number_owners = 8
        payment_token = None
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, estimated_payment)
        self.assertEqual(safe_creation_estimate.payment_token, NULL_ADDRESS)

        payment_token = get_eth_address_with_key()[0]
        with self.assertRaisesMessage(InvalidPaymentToken, payment_token):
            self.safe_creation_service.estimate_safe_creation2(
                number_owners, payment_token)

        number_tokens = 1000
        owner = Account.create()
        erc20 = self.deploy_example_erc20(number_tokens, owner.address)
        number_owners = 4
        payment_token = erc20.address
        payment_token_db = TokenFactory(address=payment_token,
                                        fixed_eth_conversion=0.1)
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, estimated_payment)
        self.assertEqual(safe_creation_estimate.payment_token, payment_token)
Beispiel #15
0
    def test_safe_creation(self):
        s = generate_valid_s()
        owner1, _ = get_eth_address_with_key()
        owner2, _ = get_eth_address_with_key()
        serializer = SafeCreationSerializer(data={
            's': s,
            'owners': [owner1, owner2],
            'threshold': 2
        })
        self.assertTrue(serializer.is_valid())
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        response_json = response.json()
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        deployer = response_json['deployer']
        self.assertTrue(check_checksum(deployer))
        self.assertTrue(check_checksum(response_json['safe']))
        self.assertTrue(check_checksum(response_json['funder']))
        self.assertEqual(response_json['paymentToken'], NULL_ADDRESS)
        self.assertGreater(int(response_json['payment']), 0)

        self.assertTrue(
            SafeContract.objects.filter(address=response.data['safe']))
        self.assertTrue(SafeCreation.objects.filter(owners__contains=[owner1]))
        safe_creation = SafeCreation.objects.get(deployer=deployer)
        self.assertEqual(safe_creation.payment_token, None)
        # Payment includes deployment gas + gas to send eth to the deployer
        self.assertGreater(safe_creation.payment,
                           safe_creation.wei_deploy_cost())

        serializer = SafeCreationSerializer(data={
            's': -1,
            'owners': [owner1, owner2],
            'threshold': 2
        })
        self.assertFalse(serializer.is_valid())
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
Beispiel #16
0
    def test_send_previously_approved_tx(self):
        number_owners = 4
        accounts = [self.create_account(initial_ether=0.01) for _ in range(number_owners)]
        accounts.sort(key=lambda x: x.address.lower())
        owners = [account.address for account in accounts]

        safe_creation = self.deploy_test_safe(threshold=2, owners=owners,
                                              initial_funding_wei=self.w3.toWei(0.01, 'ether'))
        safe_address = safe_creation.safe_address
        safe = Safe(safe_address, self.ethereum_client)
        safe_instance = get_safe_contract(self.w3, safe_address)

        to, _ = get_eth_address_with_key()
        value = self.w3.toWei(0.001, 'ether')
        data = b''
        operation = 0
        safe_tx_gas = 500000
        data_gas = 500000
        gas_price = 1
        gas_token = NULL_ADDRESS
        refund_receiver = NULL_ADDRESS
        nonce = safe.retrieve_nonce()

        self.assertEqual(nonce, 0)

        safe_tx_hash = 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

        safe_tx_contract_hash = safe_instance.functions.getTransactionHash(to, value, data, operation,
                                                                           safe_tx_gas, data_gas, gas_price, gas_token,
                                                                           refund_receiver, nonce).call()

        self.assertEqual(safe_tx_hash, safe_tx_contract_hash)

        approve_hash_fn = safe_instance.functions.approveHash(safe_tx_hash)
        for account in accounts[:2]:
            self.send_tx(approve_hash_fn.buildTransaction({'from': account.address}), account)

        for owner in (owners[0], owners[1]):
            is_approved = safe.retrieve_is_hash_approved(owner, safe_tx_hash)
            self.assertTrue(is_approved)

        # Prepare signatures. v must be 1 for previously signed and r the owner
        signatures = (1, int(owners[0], 16), 0), (1, int(owners[1], 16), 0)
        signature_bytes = signatures_to_bytes(signatures)

        safe.send_multisig_tx(to, value, data, operation, safe_tx_gas,
                              data_gas, gas_price, gas_token, refund_receiver, signature_bytes,
                              self.ethereum_test_account.key)

        balance = self.w3.eth.get_balance(to)
        self.assertEqual(value, balance)
Beispiel #17
0
class TokenFactory(DjangoModelFactory):
    class Meta:
        model = models.Token

    address = factory.LazyFunction(lambda: get_eth_address_with_key()[0])
    name = factory.Faker("cryptocurrency_name")
    symbol = factory.Faker("cryptocurrency_code")
    description = factory.Faker("catch_phrase")
    decimals = 18
    logo_uri = ""
    website_uri = ""
    gas = True
    fixed_eth_conversion = 1
Beispiel #18
0
    def test_ethereum_address_field(self):
        address, _ = get_eth_address_with_key()
        self.assertTrue(check_checksum(address))
        ethereum_address = EthereumAddress.objects.create(value=address)
        ethereum_address.refresh_from_db()
        self.assertTrue(check_checksum(ethereum_address.value))
        self.assertEqual(address, ethereum_address.value)

        ethereum_address = EthereumAddress.objects.create(value=None)
        ethereum_address.refresh_from_db()
        self.assertIsNone(ethereum_address.value)

        with self.assertRaises(Exception):
            EthereumAddress.objects.create(value='0x23')
Beispiel #19
0
    def test_safe_multisig_tx_estimate_serializer(self):
        safe_address, _ = get_eth_address_with_key()
        eth_address, _ = get_eth_address_with_key()
        data = {
            "safe": safe_address,
            "to": None,
            "data": None,
            "value": 1,
            "operation": 0,
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)

        # To and data cannot be empty
        self.assertFalse(serializer.is_valid())

        data = {
            "safe": safe_address,
            "to": eth_address,
            "data": "0x00",
            "value": 1,
            "operation": 2,
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation cannot be contract creation and to set
        self.assertFalse(serializer.is_valid())

        # Create Operation has been disabled
        data = {
            "safe": safe_address,
            "to": None,
            "data": "0x00",
            "value": 1,
            "operation": 2,
        }
        serializer = SafeMultisigEstimateTxSerializer(data=data)
        # Operation is not contract creation and to is not empty
        self.assertFalse(serializer.is_valid())
    def test_safe_multisig_tx_errors(self):
        my_safe_address = get_eth_address_with_invalid_checksum()
        response = self.client.post(reverse('v1:safe-multisig-txs', args=(my_safe_address,)),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)

        my_safe_address, _ = get_eth_address_with_key()
        response = self.client.post(reverse('v1:safe-multisig-txs', args=(my_safe_address,)),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        my_safe_address = self.create2_test_safe_in_db().safe.address
        response = self.client.post(reverse('v1:safe-multisig-txs', args=(my_safe_address,)),
                                    data={},
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Beispiel #21
0
    def test_ethereum_address_field(self):
        valid_address, _ = get_eth_address_with_key()
        for value in [
                '0x674647242239941B2D35368E66A4EDc39b161Da1',
                '0x40f3F89639Bffc7B23Ca5d9FCb9ed9a9c579664A', valid_address,
                None
        ]:
            serializer = EthereumAddressSerializerTest(data={'value': value})
            self.assertTrue(serializer.is_valid())
            self.assertEqual(value, serializer.data['value'])

        invalid_address = get_eth_address_with_invalid_checksum()
        for not_valid_value in [
                '0x674647242239941B2D35368E66A4EDc39b161DA1', invalid_address,
                '0x0000000000000000000000000000000000000000',
                '0x0000000000000000000000000000000000000001', '0xABC', '0xJK'
        ]:
            serializer = EthereumAddressSerializerTest(
                data={'value': not_valid_value})
            self.assertFalse(serializer.is_valid())
    def test_estimate_safe_creation2(self):
        gas_price = self.safe_creation_service._get_configured_gas_price()

        number_owners = 4
        payment_token = None
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, 0)
        self.assertEqual(safe_creation_estimate.payment_token, NULL_ADDRESS)
        estimated_payment = safe_creation_estimate.payment

        number_owners = 8
        payment_token = None
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, estimated_payment)
        self.assertEqual(safe_creation_estimate.payment_token, NULL_ADDRESS)

        payment_token = get_eth_address_with_key()[0]
        with self.assertRaisesMessage(InvalidPaymentToken, payment_token):
            self.safe_creation_service.estimate_safe_creation2(
                number_owners, payment_token)

        number_tokens = 1000
        owner = Account.create()
        erc20 = self.deploy_example_erc20(number_tokens, owner.address)
        number_owners = 4
        payment_token = erc20.address
        payment_token_db = TokenFactory(address=payment_token,
                                        fixed_eth_conversion=0.1)
        safe_creation_estimate = self.safe_creation_service.estimate_safe_creation2(
            number_owners, payment_token)
        self.assertGreater(safe_creation_estimate.gas, 0)
        self.assertEqual(safe_creation_estimate.gas_price, gas_price)
        self.assertGreater(safe_creation_estimate.payment, estimated_payment)
        self.assertEqual(safe_creation_estimate.payment_token, payment_token)
Beispiel #23
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'])
Beispiel #24
0
    def test_send_multisig_tx_gas_token(self):
        # Create safe with one owner, fund the safe and the owner with `safe_balance`
        receiver, _ = get_eth_address_with_key()
        threshold = 1
        funder_account = self.ethereum_test_account
        funder = funder_account.address
        safe_balance_ether = 0.02
        safe_balance = self.w3.toWei(safe_balance_ether, "ether")
        owner_account = self.create_account(initial_ether=safe_balance_ether)
        owner = owner_account.address

        safe = self.deploy_test_safe(threshold=threshold,
                                     owners=[owner],
                                     initial_funding_wei=safe_balance)
        my_safe_address = safe.address

        # Give erc20 tokens to the funder
        amount_token = int(1e18)
        erc20_contract = self.deploy_example_erc20(amount_token, funder)
        self.assertEqual(
            self.ethereum_client.erc20.get_balance(funder,
                                                   erc20_contract.address),
            amount_token,
        )

        signature_packed = signature_to_bytes(1, int(owner, 16), 0)

        to = receiver
        value = safe_balance
        data = HexBytes("")
        operation = 0
        safe_tx_gas = 100000
        base_gas = 300000
        gas_price = 2
        gas_token = erc20_contract.address
        refund_receiver = NULL_ADDRESS

        with self.assertRaises(CouldNotPayGasWithToken):
            safe.send_multisig_tx(
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                base_gas,
                gas_price,
                gas_token,
                refund_receiver,
                signature_packed,
                tx_sender_private_key=owner_account.key,
                tx_gas_price=self.gas_price,
            )

        # Give erc20 tokens to the safe
        self.ethereum_client.erc20.send_tokens(my_safe_address, amount_token,
                                               erc20_contract.address,
                                               funder_account.key)

        safe.send_multisig_tx(
            to,
            value,
            data,
            operation,
            safe_tx_gas,
            base_gas,
            gas_price,
            gas_token,
            refund_receiver,
            signature_packed,
            tx_sender_private_key=owner_account.key,
            tx_gas_price=self.gas_price,
        )

        safe_token_balance = self.ethereum_client.erc20.get_balance(
            my_safe_address, erc20_contract.address)

        # Token was used for tx gas costs. Sender must have some tokens now and safe should have less
        self.assertLess(safe_token_balance, amount_token)
        owner_token_balance = self.ethereum_client.erc20.get_balance(
            owner, erc20_contract.address)
        self.assertGreater(owner_token_balance, 0)

        # All ether on safe was transferred to receiver
        receiver_balance = self.w3.eth.get_balance(receiver)
        self.assertEqual(receiver_balance, safe_balance)
Beispiel #25
0
    def test_send_multisig_tx(self):
        # Create Safe
        w3 = self.w3
        funder_account = self.ethereum_test_account
        funder = funder_account.address
        owners_with_keys = [
            get_eth_address_with_key(),
            get_eth_address_with_key()
        ]
        # Signatures must be sorted!
        owners_with_keys.sort(key=lambda x: x[0].lower())
        owners = [x[0] for x in owners_with_keys]
        keys = [x[1] for x in owners_with_keys]
        threshold = len(owners_with_keys)

        safe = self.deploy_test_safe(threshold=threshold, owners=owners)
        my_safe_address = safe.address

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

        # Send something to the owner[0], who will be sending the tx
        owner0_balance = safe_balance
        self.send_tx({
            "to": owners[0],
            "value": owner0_balance
        }, funder_account)

        my_safe_contract = get_safe_contract(w3, my_safe_address)
        safe = Safe(my_safe_address, self.ethereum_client)

        to = funder
        value = safe_balance // 2
        data = HexBytes("")
        operation = 0
        safe_tx_gas = 100000
        base_gas = 300000
        gas_price = 1
        gas_token = NULL_ADDRESS
        refund_receiver = NULL_ADDRESS
        nonce = None
        safe_multisig_tx = safe.build_multisig_tx(
            to=to,
            value=value,
            data=data,
            operation=operation,
            safe_tx_gas=safe_tx_gas,
            base_gas=base_gas,
            gas_price=gas_price,
            gas_token=gas_token,
            refund_receiver=refund_receiver,
            safe_nonce=nonce,
        )
        safe_multisig_tx_hash = safe_multisig_tx.safe_tx_hash

        nonce = safe.retrieve_nonce()
        self.assertEqual(
            safe.build_multisig_tx(
                to=to,
                value=value,
                data=data,
                operation=operation,
                safe_tx_gas=safe_tx_gas,
                base_gas=base_gas,
                gas_price=gas_price,
                gas_token=gas_token,
                refund_receiver=refund_receiver,
                safe_nonce=nonce,
            ).safe_tx_hash,
            safe_multisig_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,
            base_gas,
            gas_price,
            gas_token,
            refund_receiver,
            nonce,
        ).call()

        self.assertEqual(safe_multisig_tx_hash, contract_multisig_tx_hash)

        for private_key in keys:
            safe_multisig_tx.sign(private_key)

        signatures = safe_multisig_tx.signatures
        self.assertEqual(set(safe_multisig_tx.signers), set(owners))

        # Check owners are the same
        contract_owners = my_safe_contract.functions.getOwners().call()
        self.assertEqual(set(contract_owners), set(owners))
        self.assertEqual(w3.eth.get_balance(owners[0]), owner0_balance)

        # with self.assertRaises(CouldNotPayGasWithEther):
        # Ganache v7 does not raise CouldNotPayGasWithEther anymore
        with self.assertRaises(InvalidInternalTx):
            safe.send_multisig_tx(
                to,
                value,
                data,
                operation,
                safe_tx_gas,
                base_gas,
                gas_price,
                gas_token,
                refund_receiver,
                signatures,
                tx_sender_private_key=keys[0],
                tx_gas_price=self.gas_price,
            )

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

        ethereum_tx_sent = safe.send_multisig_tx(
            to,
            value,
            data,
            operation,
            safe_tx_gas,
            base_gas,
            gas_price,
            gas_token,
            refund_receiver,
            signatures,
            tx_sender_private_key=keys[0],
            tx_gas_price=self.gas_price,
        )

        tx_receipt = w3.eth.wait_for_transaction_receipt(
            ethereum_tx_sent.tx_hash)
        self.assertTrue(tx_receipt["status"])
        owner0_new_balance = w3.eth.get_balance(owners[0])
        gas_used = tx_receipt["gasUsed"]
        gas_cost = gas_used * self.gas_price
        estimated_payment = (base_gas + gas_used) * gas_price
        real_payment = owner0_new_balance - (owner0_balance - gas_cost)
        # Estimated payment will be bigger, because it uses all the tx gas. Real payment only uses gas left
        # in the point of calculation of the payment, so it will be slightly lower
        self.assertTrue(estimated_payment > real_payment > 0)
        self.assertTrue(owner0_new_balance > owner0_balance -
                        ethereum_tx_sent.tx["gas"] * self.gas_price)
        self.assertEqual(safe.retrieve_nonce(), 1)
Beispiel #26
0
    def test_safe_multisig_tx_post_gas_token(self):
        # Create Safe ------------------------------------------------
        w3 = self.ethereum_client.w3
        safe_balance = w3.toWei(0.01, 'ether')
        owner0_balance = safe_balance
        owner_account = self.create_account(initial_wei=owner0_balance)
        self.assertEqual(self.w3.eth.getBalance(owner_account.address),
                         owner0_balance)
        owner = owner_account.address
        threshold = 1

        safe_creation = self.deploy_test_safe(owners=[owner],
                                              threshold=threshold,
                                              initial_funding_wei=safe_balance)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)

        # Get tokens for the safe
        safe_token_balance = int(1e18)
        erc20_contract = self.deploy_example_erc20(safe_token_balance,
                                                   my_safe_address)

        # Safe prepared --------------------------------------------
        to, _ = get_eth_address_with_key()
        value = safe_balance
        tx_data = None
        operation = 0
        refund_receiver = None
        nonce = 0
        gas_token = erc20_contract.address

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "gasToken": gas_token
        }

        # Get estimation for gas. Token does not exist
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertEqual('InvalidGasToken: %s' % gas_token,
                         response.json()['exception'])

        # Create token
        token_model = TokenFactory(address=gas_token)
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        estimation_json = response.json()

        safe_tx_gas = estimation_json['safeTxGas'] + estimation_json[
            'operationalGas']
        data_gas = estimation_json['dataGas']
        gas_price = estimation_json['gasPrice']
        gas_token = estimation_json['gasToken']

        multisig_tx_hash = SafeTx(None,
                                  my_safe_address,
                                  to,
                                  value,
                                  tx_data,
                                  operation,
                                  safe_tx_gas,
                                  data_gas,
                                  gas_price,
                                  gas_token,
                                  refund_receiver,
                                  safe_nonce=nonce).safe_tx_hash

        signatures = [
            w3.eth.account.signHash(multisig_tx_hash, private_key)
            for private_key in [owner_account.privateKey]
        ]
        signatures_json = [{
            'v': s['v'],
            'r': s['r'],
            's': s['s']
        } for s in signatures]

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "signatures": signatures_json
        }

        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        tx_hash = response.json()['transactionHash'][2:]  # Remove leading 0x
        safe_multisig_tx = SafeMultisigTx.objects.get(
            ethereum_tx__tx_hash=tx_hash)
        self.assertEqual(safe_multisig_tx.to, to)
        self.assertEqual(safe_multisig_tx.value, value)
        self.assertEqual(safe_multisig_tx.data, tx_data)
        self.assertEqual(safe_multisig_tx.operation, operation)
        self.assertEqual(safe_multisig_tx.safe_tx_gas, safe_tx_gas)
        self.assertEqual(safe_multisig_tx.data_gas, data_gas)
        self.assertEqual(safe_multisig_tx.gas_price, gas_price)
        self.assertEqual(safe_multisig_tx.gas_token, gas_token)
        self.assertEqual(safe_multisig_tx.nonce, nonce)
Beispiel #27
0
    def test_safe_multisig_tx_post(self):
        # Create Safe ------------------------------------------------
        w3 = self.ethereum_client.w3
        safe_balance = w3.toWei(0.01, 'ether')
        owner0_balance = safe_balance
        accounts = [
            self.create_account(initial_wei=owner0_balance),
            self.create_account(initial_wei=owner0_balance)
        ]
        # 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,
                                              initial_funding_wei=safe_balance)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)

        # Safe prepared --------------------------------------------
        to, _ = get_eth_address_with_key()
        value = safe_balance // 2
        tx_data = None
        operation = 0
        refund_receiver = None
        nonce = 0

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
        }

        # Get estimation for gas
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        estimation_json = response.json()

        safe_tx_gas = estimation_json['safeTxGas'] + estimation_json[
            'operationalGas']
        data_gas = estimation_json['dataGas']
        gas_price = estimation_json['gasPrice']
        gas_token = estimation_json['gasToken']

        multisig_tx_hash = SafeTx(None,
                                  my_safe_address,
                                  to,
                                  value,
                                  tx_data,
                                  operation,
                                  safe_tx_gas,
                                  data_gas,
                                  gas_price,
                                  gas_token,
                                  refund_receiver,
                                  safe_nonce=nonce).safe_tx_hash

        signatures = [
            account.signHash(multisig_tx_hash) for account in accounts
        ]
        signatures_json = [{
            'v': s['v'],
            'r': s['r'],
            's': s['s']
        } for s in signatures]

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "signatures": signatures_json
        }

        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        tx_hash = response.json()['transactionHash'][2:]  # Remove leading 0x
        safe_multisig_tx = SafeMultisigTx.objects.get(
            ethereum_tx__tx_hash=tx_hash)
        self.assertEqual(safe_multisig_tx.to, to)
        self.assertEqual(safe_multisig_tx.value, value)
        self.assertEqual(safe_multisig_tx.data, tx_data)
        self.assertEqual(safe_multisig_tx.operation, operation)
        self.assertEqual(safe_multisig_tx.safe_tx_gas, safe_tx_gas)
        self.assertEqual(safe_multisig_tx.data_gas, data_gas)
        self.assertEqual(safe_multisig_tx.gas_price, gas_price)
        self.assertEqual(safe_multisig_tx.gas_token, None)
        self.assertEqual(safe_multisig_tx.nonce, nonce)
        signature_pairs = [(s['v'], s['r'], s['s']) for s in signatures]
        signatures_packed = signatures_to_bytes(signature_pairs)
        self.assertEqual(bytes(safe_multisig_tx.signatures), signatures_packed)

        # Send the same tx again
        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertTrue('exists' in response.data['exception'])
Beispiel #28
0
    def test_safe_creation_with_payment_token(self):
        s = generate_valid_s()
        owner1, _ = get_eth_address_with_key()
        owner2, _ = get_eth_address_with_key()
        payment_token, _ = get_eth_address_with_key()
        serializer = SafeCreationSerializer(
            data={
                's': s,
                'owners': [owner1, owner2],
                'threshold': 2,
                'payment_token': payment_token,
            })
        self.assertTrue(serializer.is_valid())
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        response_json = response.json()
        self.assertIn('InvalidPaymentToken', response_json['exception'])
        self.assertIn(payment_token, response_json['exception'])

        # With previous versions of ganache it failed, because token was on DB but not in blockchain,
        # so gas cannot be estimated. With new versions of ganache estimation is working
        token_model = TokenFactory(address=payment_token,
                                   fixed_eth_conversion=0.1)
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        erc20_contract = self.deploy_example_erc20(10000, NULL_ADDRESS)
        payment_token = erc20_contract.address
        serializer = SafeCreationSerializer(
            data={
                's': s,
                'owners': [owner1, owner2],
                'threshold': 2,
                'payment_token': payment_token,
            })
        self.assertTrue(serializer.is_valid())
        token_model = TokenFactory(address=payment_token,
                                   fixed_eth_conversion=0.1)
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        response_json = response.json()
        deployer = response_json['deployer']
        self.assertTrue(check_checksum(deployer))
        self.assertTrue(check_checksum(response_json['safe']))
        self.assertEqual(response_json['paymentToken'], payment_token)

        self.assertTrue(
            SafeContract.objects.filter(address=response.data['safe']))
        safe_creation = SafeCreation.objects.get(deployer=deployer)
        self.assertIn(owner1, safe_creation.owners)
        self.assertEqual(safe_creation.payment_token, payment_token)
        self.assertGreater(safe_creation.payment,
                           safe_creation.wei_deploy_cost())

        # Check that payment is more than with ether
        token_payment = response_json['payment']
        serializer = SafeCreationSerializer(data={
            's': s,
            'owners': [owner1, owner2],
            'threshold': 2,
        })
        self.assertTrue(serializer.is_valid())
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_json = response.json()
        payment_using_ether = response_json['payment']
        self.assertGreater(token_payment, payment_using_ether)

        # Check that token with fixed conversion price to 1 is a little higher than with ether
        # (We need to pay for storage for token transfer, as funder does not own any token yet)
        erc20_contract = self.deploy_example_erc20(10000, NULL_ADDRESS)
        payment_token = erc20_contract.address
        token_model = TokenFactory(address=payment_token,
                                   fixed_eth_conversion=1)
        serializer = SafeCreationSerializer(
            data={
                's': s,
                'owners': [owner1, owner2],
                'threshold': 2,
                'payment_token': payment_token
            })
        self.assertTrue(serializer.is_valid())
        response = self.client.post(reverse('v1:safe-creation'),
                                    data=serializer.data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_json = response.json()
        deployer = response_json['deployer']
        payment_using_token = response_json['payment']
        self.assertGreater(payment_using_token, payment_using_ether)
        safe_creation = SafeCreation.objects.get(deployer=deployer)
        # Payment includes also the gas to send ether to the safe deployer
        self.assertGreater(safe_creation.payment,
                           safe_creation.wei_deploy_cost())
    def test_safe_multisig_tx_serializer(self):
        safe = get_eth_address_with_key()[0]
        to = None
        value = int(10e18)
        tx_data = None
        operation = 0
        safe_tx_gas = 1
        data_gas = 1
        gas_price = 1
        gas_token = None
        refund_receiver = None
        nonce = 0

        data = {
            "safe": safe,
            "to": to,
            "value": value,  # 1 ether
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "signatures": [
                {
                    'r': 5,
                    's': 7,
                    'v': 27
                },
                {
                    'r': 17,
                    's': 29,
                    'v': 28
                }]}
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertFalse(serializer.is_valid())  # Less signatures than threshold

        # Signatures must be sorted!
        accounts = [Account.create() for _ in range(2)]
        accounts.sort(key=lambda x: x.address.lower())

        safe = get_eth_address_with_key()[0]
        data['safe'] = safe

        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertFalse(serializer.is_valid())  # To and data cannot both be null

        tx_data = HexBytes('0xabcd')
        data['data'] = tx_data.hex()
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertFalse(serializer.is_valid())  # Operation is not create, but no to provided

        # Now we fix the signatures
        to = accounts[-1].address
        data['to'] = to
        multisig_tx_hash = SafeTx(
            None,
            safe,
            to,
            value,
            tx_data,
            operation,
            safe_tx_gas,
            data_gas,
            gas_price,
            gas_token,
            refund_receiver,
            safe_nonce=nonce
        ).safe_tx_hash

        signatures = [account.signHash(multisig_tx_hash) for account in accounts]
        data['signatures'] = [{'v': s.v, 'r': s.r, 's': s.s} for s in signatures]
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertTrue(serializer.is_valid())

        data = {
            "safe": safe,
            "to": to,
            "value": value,  # 1 ether
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "refund_receiver": accounts[0].address,  # Refund receiver must be empty or relay service sender
            "signatures": [
                {
                    'r': 5,
                    's': 7,
                    'v': 27
                }]
        }
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertFalse(serializer.is_valid())

        data['refund_receiver'] = Account.from_key(settings.SAFE_TX_SENDER_PRIVATE_KEY).address
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertTrue(serializer.is_valid())

        data['refund_receiver'] = NULL_ADDRESS
        serializer = SafeRelayMultisigTxSerializer(data=data)
        self.assertTrue(serializer.is_valid())
    def test_safe_creation_tx_builder_with_token_payment(self):
        logger.info("Test Safe Proxy creation With Gas Payment".center(LOG_TITLE_WIDTH, '-'))
        w3 = self.w3

        s = generate_valid_s()

        erc20_deployer = Account.create()
        funder_account = self.ethereum_test_account

        # Send something to the erc20 deployer
        self.send_tx({
            'to': erc20_deployer.address,
            'value': w3.toWei(1, 'ether')
        }, funder_account)

        funder = funder_account.address
        owners = [get_eth_address_with_key()[0] for _ in range(2)]
        threshold = len(owners) - 1
        gas_price = self.gas_price
        token_amount = int(1e18)
        erc20_contract = self.deploy_example_erc20(token_amount, erc20_deployer.address)
        self.assertEqual(erc20_contract.functions.balanceOf(erc20_deployer.address).call(), token_amount)

        safe_creation_tx = SafeCreationTx(w3=w3,
                                          owners=owners,
                                          threshold=threshold,
                                          signature_s=s,
                                          master_copy=self.safe_contract_V0_0_1_address,
                                          gas_price=gas_price,
                                          payment_token=erc20_contract.address,
                                          funder=funder)

        # In this test we will pretend that ether value = token value, so we send tokens as ether payment
        payment = safe_creation_tx.payment
        deployer_address = safe_creation_tx.deployer_address
        safe_address = safe_creation_tx.safe_address
        logger.info("Send %d tokens to safe %s", payment, safe_address)
        self.send_tx(erc20_contract.functions.transfer(safe_address,
                                                       payment).buildTransaction({'from': erc20_deployer.address}),
                     erc20_deployer)
        self.assertEqual(erc20_contract.functions.balanceOf(safe_address).call(), payment)

        logger.info("Send %d ether to deployer %s", w3.fromWei(payment, 'ether'), deployer_address)
        self.send_tx({
            'to': safe_creation_tx.deployer_address,
            'value': safe_creation_tx.payment
        }, funder_account)

        logger.info("Create proxy contract with address %s", safe_creation_tx.safe_address)
        funder_balance = w3.eth.getBalance(funder)

        # This tx will create the Safe Proxy and return tokens to the funder
        tx_hash = w3.eth.sendRawTransaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_address)
        self.assertEqual(w3.eth.getBalance(funder), funder_balance)
        self.assertEqual(erc20_contract.functions.balanceOf(funder).call(), payment)
        self.assertEqual(erc20_contract.functions.balanceOf(safe_address).call(), 0)

        logger.info("Deployer account has still %d gwei left (will be lost)",
                    w3.fromWei(w3.eth.getBalance(safe_creation_tx.deployer_address), 'gwei'))

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        self.assertEqual(deployed_safe_proxy_contract.functions.getThreshold().call(), threshold)
        self.assertEqual(deployed_safe_proxy_contract.functions.getOwners().call(), owners)

        # Payment should be <= when payment_token_eth_value is 1.0
        # Funder will already have tokens so no storage need to be paid)
        safe_creation_tx_2 = SafeCreationTx(w3=w3,
                                            owners=owners,
                                            threshold=threshold,
                                            signature_s=s,
                                            master_copy=self.safe_contract_V0_0_1_address,
                                            gas_price=gas_price,
                                            payment_token=erc20_contract.address,
                                            payment_token_eth_value=1.0,
                                            funder=funder)
        self.assertLessEqual(safe_creation_tx_2.payment, safe_creation_tx.payment)

        # Now payment should be equal when payment_token_eth_value is 1.0
        safe_creation_tx_3 = SafeCreationTx(w3=w3,
                                            owners=owners,
                                            threshold=threshold,
                                            signature_s=s,
                                            master_copy=self.safe_contract_V0_0_1_address,
                                            gas_price=gas_price,
                                            payment_token=erc20_contract.address,
                                            payment_token_eth_value=1.0,
                                            funder=funder)
        self.assertEqual(safe_creation_tx_3.payment, safe_creation_tx_2.payment)

        # Check that payment is less when payment_token_eth_value is set(token value > ether)
        safe_creation_tx_4 = SafeCreationTx(w3=w3,
                                            owners=owners,
                                            threshold=threshold,
                                            signature_s=s,
                                            master_copy=self.safe_contract_V0_0_1_address,
                                            gas_price=gas_price,
                                            payment_token=erc20_contract.address,
                                            payment_token_eth_value=1.1,
                                            funder=funder)
        self.assertLess(safe_creation_tx_4.payment, safe_creation_tx.payment)

        # Check that payment is more when payment_token_eth_value is set(token value < ether)
        safe_creation_tx_5 = SafeCreationTx(w3=w3,
                                            owners=owners,
                                            threshold=threshold,
                                            signature_s=s,
                                            master_copy=self.safe_contract_V0_0_1_address,
                                            gas_price=gas_price,
                                            payment_token=erc20_contract.address,
                                            payment_token_eth_value=0.1,
                                            funder=funder)
        self.assertGreater(safe_creation_tx_5.payment, safe_creation_tx.payment)