Skip to content

Commit 7fb2ba9

Browse files
Add test with nested safes
1 parent b8127fa commit 7fb2ba9

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

safe_transaction_service/history/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def calculate_hash_and_preimage(
9393
9494
:param delegate_address:
9595
:param chain_id:
96-
:param previous_totp:
96+
:param previous_totp: if true calculate previous totp interval
9797
:return: Hash for the EIP712 generated object from the provided parameters with the preimage
9898
"""
9999
totp = cls.calculate_totp(previous=previous_totp)

safe_transaction_service/history/serializers.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ def validate_delegator_signature(
452452
) -> bool:
453453
ethereum_client = get_auto_ethereum_client()
454454
chain_id = ethereum_client.get_chain_id()
455+
any_valid_signature_found = False
456+
455457
# Accept a message with the current topt and the previous totp (to prevent replay attacks)
456458
for previous_totp, chain_id in list(
457459
itertools.product((True, False), (chain_id, None))
@@ -473,13 +475,15 @@ def validate_delegator_signature(
473475
)
474476
safe_signature = safe_signatures[0]
475477
owner = safe_signature.owner
476-
if not safe_signature.is_valid(ethereum_client, owner):
477-
raise ValidationError(
478-
f"Signature of type={safe_signature.signature_type.name} "
479-
f"for signer={signer} is not valid"
480-
)
481-
if owner == signer:
482-
return True
478+
479+
if safe_signature.is_valid(ethereum_client, owner):
480+
any_valid_signature_found = True
481+
if owner == signer:
482+
return True
483+
484+
if not any_valid_signature_found:
485+
raise ValidationError(f"No valid signature found for signer={signer}")
486+
483487
return False
484488

485489

safe_transaction_service/history/tests/test_views_v2.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def test_delegates_post(self):
293293
data["signature"] = to_0x_hex_str(signature)
294294
response = self.client.post(url, format="json", data=data)
295295
self.assertIn(
296-
f"Signature of type=CONTRACT_SIGNATURE for signer={delegator.address} is not valid",
296+
f"No valid signature found for signer={delegator.address}",
297297
response.data["non_field_errors"][0],
298298
)
299299
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@@ -354,6 +354,79 @@ def test_delegate_creation_without_chain_id_with_safe(self):
354354
self.assertEqual(safe_contract_delegate.delegator, delegator.address)
355355
self.assertEqual(safe_contract_delegate.safe_contract_id, safe.address)
356356

357+
def _test_add_delegate_using_1271_signature(self, safe_deployment_fn):
358+
account_a = Account.create()
359+
account_b = Account.create()
360+
safe_owner = safe_deployment_fn(
361+
owners=[account_a.address, account_b.address], threshold=2
362+
)
363+
SafeContractFactory(address=safe_owner.address)
364+
365+
nested_safe = safe_deployment_fn(owners=[safe_owner.address])
366+
SafeContractFactory(address=nested_safe.address)
367+
368+
chain_id = self.ethereum_client.get_chain_id()
369+
delegate = Account.create()
370+
371+
_, preimage_to_sign = DelegateSignatureHelperV2.calculate_hash_and_preimage(
372+
delegate.address, chain_id, previous_totp=False
373+
)
374+
375+
safe_owner_message_hash, _ = safe_owner.get_message_hash_and_preimage(
376+
preimage_to_sign
377+
)
378+
379+
safe_owner_account_a_signature = account_a.unsafe_sign_hash(
380+
safe_owner_message_hash
381+
)["signature"]
382+
safe_owner_account_b_signature = account_b.unsafe_sign_hash(
383+
safe_owner_message_hash
384+
)["signature"]
385+
386+
# Build EIP1271 signature v=0 r=safe v=dynamic_part dynamic_part=size+owner_signature
387+
signatures_with_addresses = [
388+
(account_a.address.lower(), safe_owner_account_a_signature),
389+
(account_b.address.lower(), safe_owner_account_b_signature),
390+
]
391+
signatures_with_addresses.sort(key=lambda x: x[0])
392+
ordered_signatures = b"".join(sig for _, sig in signatures_with_addresses)
393+
394+
signature_1271 = (
395+
signature_to_bytes(
396+
0, int.from_bytes(HexBytes(safe_owner.address), byteorder="big"), 65
397+
)
398+
+ eth_abi.encode(["bytes"], [ordered_signatures])[32:]
399+
)
400+
401+
data = {
402+
"label": "Nested safe delegator",
403+
"safe": nested_safe.address,
404+
"delegate": delegate.address,
405+
"delegator": safe_owner.address,
406+
"signature": to_0x_hex_str(HexBytes(signature_1271)),
407+
}
408+
response = self.client.post(
409+
reverse("v2:history:delegates"),
410+
format="json",
411+
data=data,
412+
)
413+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
414+
self.assertEqual(SafeContractDelegate.objects.count(), 1)
415+
safe_contract_delegate = SafeContractDelegate.objects.get()
416+
self.assertEqual(safe_contract_delegate.delegate, delegate.address)
417+
self.assertEqual(safe_contract_delegate.delegator, safe_owner.address)
418+
self.assertEqual(safe_contract_delegate.safe_contract_id, nested_safe.address)
419+
420+
def test_add_delegate_using_1271_signature_v1_3_0(self):
421+
return self._test_add_delegate_using_1271_signature(
422+
self.deploy_test_safe_v1_3_0
423+
)
424+
425+
def test_add_delegate_using_1271_signature_v1_4_1(self):
426+
return self._test_add_delegate_using_1271_signature(
427+
self.deploy_test_safe_v1_4_1
428+
)
429+
357430
def test_delegates_get(self):
358431
url = reverse("v2:history:delegates")
359432
response = self.client.get(url, format="json")

0 commit comments

Comments
 (0)