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()
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())
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)
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)
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, )
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)
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)
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)
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)
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
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')
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)
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)
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'])
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)
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)
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)
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'])
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)