Skip to content

Commit 2dfbe0f

Browse files
Add exception for trusted contracts for delegate calls when creation not allowed (#2444)
* Add exception for trusted_addresses_for_delegate_call when creation check is disable * Fix test
1 parent 894b76c commit 2dfbe0f

File tree

3 files changed

+154
-111
lines changed

3 files changed

+154
-111
lines changed

safe_transaction_service/history/serializers.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
get_safe_owners,
4242
)
4343

44+
from ..contracts.models import Contract
4445
from .exceptions import InternalValidationError, NodeConnectionException
4546
from .helpers import (
4647
DelegateSignatureHelper,
@@ -193,26 +194,27 @@ def validate_origin(self, origin):
193194

194195
return origin
195196

196-
def validate_operation(self, value):
197+
def validate(self, attrs):
198+
super().validate(attrs)
199+
200+
tx_to = attrs["to"]
201+
tx_operation = attrs["operation"]
197202
if (
198203
settings.DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION
199-
and value == SafeOperationEnum.DELEGATE_CALL.value
204+
and tx_operation == SafeOperationEnum.DELEGATE_CALL.value
205+
and tx_to not in Contract.objects.trusted_addresses_for_delegate_call()
200206
):
201207
raise ValidationError("Operation DELEGATE_CALL is not allowed")
202-
return super().validate_operation(value)
203-
204-
def validate(self, attrs):
205-
super().validate(attrs)
206208

207209
ethereum_client = get_auto_ethereum_client()
208210
safe_address = attrs["safe"]
209211

210212
safe = Safe(safe_address, ethereum_client)
211213
safe_tx = safe.build_multisig_tx(
212-
attrs["to"],
214+
tx_to,
213215
attrs["value"],
214216
attrs["data"],
215-
attrs["operation"],
217+
tx_operation,
216218
attrs["safe_tx_gas"],
217219
attrs["base_gas"],
218220
attrs["gas_price"],

safe_transaction_service/history/tests/test_views.py

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,10 +1383,10 @@ def test_get_multisig_transactions_not_decoded(
13831383
self.assertEqual(response.status_code, status.HTTP_200_OK)
13841384
self.assertIsNone(response.data["results"][0]["data_decoded"])
13851385

1386-
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
13871386
ContractFactory(
13881387
address=multisig_transaction.to, trusted_for_delegate_call=True
13891388
)
1389+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
13901390
# Force don't use cache because we are not cleaning the cache on contracts change
13911391
with mock.patch(
13921392
"safe_transaction_service.history.views.settings.CACHE_VIEW_DEFAULT_TIMEOUT",
@@ -2332,62 +2332,82 @@ def test_post_multisig_transaction_with_delegate_call(self):
23322332
safe_owner_1 = Account.create()
23332333
safe = self.deploy_test_safe(owners=[safe_owner_1.address])
23342334
safe_address = safe.address
2335-
2336-
response = self.client.get(
2337-
reverse("v1:history:multisig-transactions", args=(safe_address,)),
2338-
format="json",
2339-
)
2340-
self.assertEqual(response.status_code, status.HTTP_200_OK)
2341-
self.assertEqual(response.data["count"], 0)
2342-
2343-
data = {
2344-
"to": Account.create().address,
2345-
"value": 0,
2346-
"data": "0x12121212",
2347-
"operation": 1,
2348-
"nonce": 0,
2349-
"safeTxGas": 0,
2350-
"baseGas": 0,
2351-
"gasPrice": 0,
2352-
"gasToken": "0x0000000000000000000000000000000000000000",
2353-
"refundReceiver": "0x0000000000000000000000000000000000000000",
2354-
"sender": safe_owner_1.address,
2355-
}
2356-
safe_tx = safe.build_multisig_tx(
2357-
data["to"],
2358-
data["value"],
2359-
data["data"],
2360-
data["operation"],
2361-
data["safeTxGas"],
2362-
data["baseGas"],
2363-
data["gasPrice"],
2364-
data["gasToken"],
2365-
data["refundReceiver"],
2366-
safe_nonce=data["nonce"],
2367-
)
2368-
data["contractTransactionHash"] = to_0x_hex_str(safe_tx.safe_tx_hash)
2369-
2370-
with self.settings(
2371-
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
2372-
):
2373-
response = self.client.post(
2335+
try:
2336+
response = self.client.get(
23742337
reverse("v1:history:multisig-transactions", args=(safe_address,)),
23752338
format="json",
2376-
data=data,
23772339
)
2378-
self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
2379-
2380-
with self.settings(
2381-
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=False
2382-
):
2383-
response = self.client.post(
2384-
reverse("v1:history:multisig-transactions", args=(safe_address,)),
2385-
format="json",
2386-
data=data,
2340+
self.assertEqual(response.status_code, status.HTTP_200_OK)
2341+
self.assertEqual(response.data["count"], 0)
2342+
2343+
data = {
2344+
"to": Account.create().address,
2345+
"value": 0,
2346+
"data": "0x12121212",
2347+
"operation": SafeOperationEnum.DELEGATE_CALL.value,
2348+
"nonce": 0,
2349+
"safeTxGas": 0,
2350+
"baseGas": 0,
2351+
"gasPrice": 0,
2352+
"gasToken": "0x0000000000000000000000000000000000000000",
2353+
"refundReceiver": "0x0000000000000000000000000000000000000000",
2354+
"sender": safe_owner_1.address,
2355+
}
2356+
safe_tx = safe.build_multisig_tx(
2357+
data["to"],
2358+
data["value"],
2359+
data["data"],
2360+
data["operation"],
2361+
data["safeTxGas"],
2362+
data["baseGas"],
2363+
data["gasPrice"],
2364+
data["gasToken"],
2365+
data["refundReceiver"],
2366+
safe_nonce=data["nonce"],
23872367
)
2388-
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2389-
multisig_transaction_db = MultisigTransaction.objects.first()
2390-
self.assertEqual(multisig_transaction_db.operation, 1)
2368+
data["contractTransactionHash"] = to_0x_hex_str(safe_tx.safe_tx_hash)
2369+
2370+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
2371+
# Disable creation with delegate call and not trusted contract
2372+
with self.settings(
2373+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
2374+
):
2375+
response = self.client.post(
2376+
reverse("v1:history:multisig-transactions", args=(safe_address,)),
2377+
format="json",
2378+
data=data,
2379+
)
2380+
self.assertEqual(
2381+
response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY
2382+
)
2383+
2384+
# Enable creation with delegate call
2385+
with self.settings(
2386+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=False
2387+
):
2388+
response = self.client.post(
2389+
reverse("v1:history:multisig-transactions", args=(safe_address,)),
2390+
format="json",
2391+
data=data,
2392+
)
2393+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2394+
multisig_transaction_db = MultisigTransaction.objects.first()
2395+
self.assertEqual(multisig_transaction_db.operation, 1)
2396+
2397+
# Disable creation with delegate call and trusted contract
2398+
ContractFactory(address=data["to"], trusted_for_delegate_call=True)
2399+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
2400+
with self.settings(
2401+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
2402+
):
2403+
response = self.client.post(
2404+
reverse("v1:history:multisig-transactions", args=(safe_address,)),
2405+
format="json",
2406+
data=data,
2407+
)
2408+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2409+
finally:
2410+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
23912411

23922412
def test_safe_balances_view(self):
23932413
safe_address = Account.create().address

safe_transaction_service/history/tests/test_views_v2.py

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,61 +1958,82 @@ def test_post_multisig_transaction_with_delegate_call(self):
19581958
safe = self.deploy_test_safe(owners=[safe_owner_1.address])
19591959
safe_address = safe.address
19601960

1961-
response = self.client.get(
1962-
reverse("v2:history:multisig-transactions", args=(safe_address,)),
1963-
format="json",
1964-
)
1965-
self.assertEqual(response.status_code, status.HTTP_200_OK)
1966-
self.assertEqual(response.data["count"], 0)
1967-
1968-
data = {
1969-
"to": Account.create().address,
1970-
"value": 0,
1971-
"data": "0x12121212",
1972-
"operation": 1,
1973-
"nonce": 0,
1974-
"safeTxGas": 0,
1975-
"baseGas": 0,
1976-
"gasPrice": 0,
1977-
"gasToken": "0x0000000000000000000000000000000000000000",
1978-
"refundReceiver": "0x0000000000000000000000000000000000000000",
1979-
"sender": safe_owner_1.address,
1980-
}
1981-
safe_tx = safe.build_multisig_tx(
1982-
data["to"],
1983-
data["value"],
1984-
data["data"],
1985-
data["operation"],
1986-
data["safeTxGas"],
1987-
data["baseGas"],
1988-
data["gasPrice"],
1989-
data["gasToken"],
1990-
data["refundReceiver"],
1991-
safe_nonce=data["nonce"],
1992-
)
1993-
data["contractTransactionHash"] = to_0x_hex_str(safe_tx.safe_tx_hash)
1994-
1995-
with self.settings(
1996-
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
1997-
):
1998-
response = self.client.post(
1961+
try:
1962+
response = self.client.get(
19991963
reverse("v2:history:multisig-transactions", args=(safe_address,)),
20001964
format="json",
2001-
data=data,
20021965
)
2003-
self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
1966+
self.assertEqual(response.status_code, status.HTTP_200_OK)
1967+
self.assertEqual(response.data["count"], 0)
20041968

2005-
with self.settings(
2006-
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=False
2007-
):
2008-
response = self.client.post(
2009-
reverse("v2:history:multisig-transactions", args=(safe_address,)),
2010-
format="json",
2011-
data=data,
1969+
data = {
1970+
"to": Account.create().address,
1971+
"value": 0,
1972+
"data": "0x12121212",
1973+
"operation": SafeOperationEnum.DELEGATE_CALL.value,
1974+
"nonce": 0,
1975+
"safeTxGas": 0,
1976+
"baseGas": 0,
1977+
"gasPrice": 0,
1978+
"gasToken": "0x0000000000000000000000000000000000000000",
1979+
"refundReceiver": "0x0000000000000000000000000000000000000000",
1980+
"sender": safe_owner_1.address,
1981+
}
1982+
safe_tx = safe.build_multisig_tx(
1983+
data["to"],
1984+
data["value"],
1985+
data["data"],
1986+
data["operation"],
1987+
data["safeTxGas"],
1988+
data["baseGas"],
1989+
data["gasPrice"],
1990+
data["gasToken"],
1991+
data["refundReceiver"],
1992+
safe_nonce=data["nonce"],
20121993
)
2013-
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2014-
multisig_transaction_db = MultisigTransaction.objects.first()
2015-
self.assertEqual(multisig_transaction_db.operation, 1)
1994+
data["contractTransactionHash"] = to_0x_hex_str(safe_tx.safe_tx_hash)
1995+
1996+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
1997+
# Disable creation with delegate call and not trusted contract
1998+
with self.settings(
1999+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
2000+
):
2001+
response = self.client.post(
2002+
reverse("v2:history:multisig-transactions", args=(safe_address,)),
2003+
format="json",
2004+
data=data,
2005+
)
2006+
self.assertEqual(
2007+
response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY
2008+
)
2009+
2010+
# Enable creation with delegate call
2011+
with self.settings(
2012+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=False
2013+
):
2014+
response = self.client.post(
2015+
reverse("v2:history:multisig-transactions", args=(safe_address,)),
2016+
format="json",
2017+
data=data,
2018+
)
2019+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2020+
multisig_transaction_db = MultisigTransaction.objects.first()
2021+
self.assertEqual(multisig_transaction_db.operation, 1)
2022+
2023+
# Disable creation with delegate call and trusted contract
2024+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
2025+
ContractFactory(address=data["to"], trusted_for_delegate_call=True)
2026+
with self.settings(
2027+
DISABLE_CREATION_MULTISIG_TRANSACTIONS_WITH_DELEGATE_CALL_OPERATION=True
2028+
):
2029+
response = self.client.post(
2030+
reverse("v2:history:multisig-transactions", args=(safe_address,)),
2031+
format="json",
2032+
data=data,
2033+
)
2034+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2035+
finally:
2036+
ContractQuerySet.cache_trusted_addresses_for_delegate_call.clear()
20162037

20172038
def test_delete_multisig_transaction(self):
20182039
owner_account = Account.create()

0 commit comments

Comments
 (0)