diff --git a/requirements.txt b/requirements.txt index 3d16bb598..160e21ec8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,5 +32,5 @@ psycogreen==1.0.2 psycopg2==2.9.7 redis==5.0.0 requests==2.31.0 -safe-eth-py[django]==5.8.0 +safe-eth-py[django]==6.0.0b1 web3==6.10.0 diff --git a/safe_transaction_service/contracts/tx_decoder.py b/safe_transaction_service/contracts/tx_decoder.py index 0b7cebc79..826c21b53 100644 --- a/safe_transaction_service/contracts/tx_decoder.py +++ b/safe_transaction_service/contracts/tx_decoder.py @@ -38,6 +38,7 @@ get_safe_V1_0_0_contract, get_safe_V1_1_1_contract, get_safe_V1_3_0_contract, + get_safe_V1_4_1_contract, get_uniswap_exchange_contract, ) from gnosis.safe.multi_send import MultiSend @@ -334,6 +335,7 @@ def get_supported_abis(self) -> Iterable[ABIFunction]: get_safe_V1_0_0_contract(self.dummy_w3).abi, get_safe_V1_1_1_contract(self.dummy_w3).abi, get_safe_V1_3_0_contract(self.dummy_w3).abi, + get_safe_V1_4_1_contract(self.dummy_w3).abi, ] # Order is important. If signature is the same (e.g. renaming of `baseGas`) last elements in the list diff --git a/safe_transaction_service/history/indexers/abis/__init__.py b/safe_transaction_service/history/indexers/abis/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/safe_transaction_service/history/indexers/abis/gnosis.py b/safe_transaction_service/history/indexers/abis/gnosis.py deleted file mode 100644 index a404bf375..000000000 --- a/safe_transaction_service/history/indexers/abis/gnosis.py +++ /dev/null @@ -1,820 +0,0 @@ -gnosis_safe_l2_v1_3_0_abi = [ - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address", - } - ], - "name": "AddedOwner", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "approvedHash", - "type": "bytes32", - }, - { - "indexed": True, - "internalType": "address", - "name": "owner", - "type": "address", - }, - ], - "name": "ApproveHash", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "handler", - "type": "address", - } - ], - "name": "ChangedFallbackHandler", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "guard", - "type": "address", - } - ], - "name": "ChangedGuard", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint256", - "name": "threshold", - "type": "uint256", - } - ], - "name": "ChangedThreshold", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "module", - "type": "address", - } - ], - "name": "DisabledModule", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "module", - "type": "address", - } - ], - "name": "EnabledModule", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "bytes32", - "name": "txHash", - "type": "bytes32", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "payment", - "type": "uint256", - }, - ], - "name": "ExecutionFailure", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "module", - "type": "address", - } - ], - "name": "ExecutionFromModuleFailure", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "module", - "type": "address", - } - ], - "name": "ExecutionFromModuleSuccess", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "bytes32", - "name": "txHash", - "type": "bytes32", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "payment", - "type": "uint256", - }, - ], - "name": "ExecutionSuccess", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address", - } - ], - "name": "RemovedOwner", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "module", - "type": "address", - }, - { - "indexed": False, - "internalType": "address", - "name": "to", - "type": "address", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "value", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "bytes", - "name": "data", - "type": "bytes", - }, - { - "indexed": False, - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - ], - "name": "SafeModuleTransaction", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "to", - "type": "address", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "value", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "bytes", - "name": "data", - "type": "bytes", - }, - { - "indexed": False, - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "safeTxGas", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "baseGas", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "gasPrice", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "address", - "name": "gasToken", - "type": "address", - }, - { - "indexed": False, - "internalType": "address payable", - "name": "refundReceiver", - "type": "address", - }, - { - "indexed": False, - "internalType": "bytes", - "name": "signatures", - "type": "bytes", - }, - { - "indexed": False, - "internalType": "bytes", - "name": "additionalInfo", - "type": "bytes", - }, - ], - "name": "SafeMultiSigTransaction", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "sender", - "type": "address", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "value", - "type": "uint256", - }, - ], - "name": "SafeReceived", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "initiator", - "type": "address", - }, - { - "indexed": False, - "internalType": "address[]", - "name": "owners", - "type": "address[]", - }, - { - "indexed": False, - "internalType": "uint256", - "name": "threshold", - "type": "uint256", - }, - { - "indexed": False, - "internalType": "address", - "name": "initializer", - "type": "address", - }, - { - "indexed": False, - "internalType": "address", - "name": "fallbackHandler", - "type": "address", - }, - ], - "name": "SafeSetup", - "type": "event", - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32", - } - ], - "name": "SignMsg", - "type": "event", - }, - {"stateMutability": "nonpayable", "type": "fallback"}, - { - "inputs": [], - "name": "VERSION", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "owner", "type": "address"}, - {"internalType": "uint256", "name": "_threshold", "type": "uint256"}, - ], - "name": "addOwnerWithThreshold", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "bytes32", "name": "hashToApprove", "type": "bytes32"} - ], - "name": "approveHash", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "", "type": "address"}, - {"internalType": "bytes32", "name": "", "type": "bytes32"}, - ], - "name": "approvedHashes", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "uint256", "name": "_threshold", "type": "uint256"} - ], - "name": "changeThreshold", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "bytes32", "name": "dataHash", "type": "bytes32"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - {"internalType": "bytes", "name": "signatures", "type": "bytes"}, - { - "internalType": "uint256", - "name": "requiredSignatures", - "type": "uint256", - }, - ], - "name": "checkNSignatures", - "outputs": [], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "bytes32", "name": "dataHash", "type": "bytes32"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - {"internalType": "bytes", "name": "signatures", "type": "bytes"}, - ], - "name": "checkSignatures", - "outputs": [], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "prevModule", "type": "address"}, - {"internalType": "address", "name": "module", "type": "address"}, - ], - "name": "disableModule", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [], - "name": "domainSeparator", - "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [{"internalType": "address", "name": "module", "type": "address"}], - "name": "enableModule", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - {"internalType": "uint256", "name": "safeTxGas", "type": "uint256"}, - {"internalType": "uint256", "name": "baseGas", "type": "uint256"}, - {"internalType": "uint256", "name": "gasPrice", "type": "uint256"}, - {"internalType": "address", "name": "gasToken", "type": "address"}, - {"internalType": "address", "name": "refundReceiver", "type": "address"}, - {"internalType": "uint256", "name": "_nonce", "type": "uint256"}, - ], - "name": "encodeTransactionData", - "outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - {"internalType": "uint256", "name": "safeTxGas", "type": "uint256"}, - {"internalType": "uint256", "name": "baseGas", "type": "uint256"}, - {"internalType": "uint256", "name": "gasPrice", "type": "uint256"}, - {"internalType": "address", "name": "gasToken", "type": "address"}, - { - "internalType": "address payable", - "name": "refundReceiver", - "type": "address", - }, - {"internalType": "bytes", "name": "signatures", "type": "bytes"}, - ], - "name": "execTransaction", - "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], - "stateMutability": "payable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - ], - "name": "execTransactionFromModule", - "outputs": [{"internalType": "bool", "name": "success", "type": "bool"}], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - ], - "name": "execTransactionFromModuleReturnData", - "outputs": [ - {"internalType": "bool", "name": "success", "type": "bool"}, - {"internalType": "bytes", "name": "returnData", "type": "bytes"}, - ], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [], - "name": "getChainId", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "start", "type": "address"}, - {"internalType": "uint256", "name": "pageSize", "type": "uint256"}, - ], - "name": "getModulesPaginated", - "outputs": [ - {"internalType": "address[]", "name": "array", "type": "address[]"}, - {"internalType": "address", "name": "next", "type": "address"}, - ], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [], - "name": "getOwners", - "outputs": [{"internalType": "address[]", "name": "", "type": "address[]"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "uint256", "name": "offset", "type": "uint256"}, - {"internalType": "uint256", "name": "length", "type": "uint256"}, - ], - "name": "getStorageAt", - "outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [], - "name": "getThreshold", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - {"internalType": "uint256", "name": "safeTxGas", "type": "uint256"}, - {"internalType": "uint256", "name": "baseGas", "type": "uint256"}, - {"internalType": "uint256", "name": "gasPrice", "type": "uint256"}, - {"internalType": "address", "name": "gasToken", "type": "address"}, - {"internalType": "address", "name": "refundReceiver", "type": "address"}, - {"internalType": "uint256", "name": "_nonce", "type": "uint256"}, - ], - "name": "getTransactionHash", - "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [{"internalType": "address", "name": "module", "type": "address"}], - "name": "isModuleEnabled", - "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], - "name": "isOwner", - "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [], - "name": "nonce", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "prevOwner", "type": "address"}, - {"internalType": "address", "name": "owner", "type": "address"}, - {"internalType": "uint256", "name": "_threshold", "type": "uint256"}, - ], - "name": "removeOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "uint256", "name": "value", "type": "uint256"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8", - }, - ], - "name": "requiredTxGas", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [{"internalType": "address", "name": "handler", "type": "address"}], - "name": "setFallbackHandler", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [{"internalType": "address", "name": "guard", "type": "address"}], - "name": "setGuard", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address[]", "name": "_owners", "type": "address[]"}, - {"internalType": "uint256", "name": "_threshold", "type": "uint256"}, - {"internalType": "address", "name": "to", "type": "address"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - {"internalType": "address", "name": "fallbackHandler", "type": "address"}, - {"internalType": "address", "name": "paymentToken", "type": "address"}, - {"internalType": "uint256", "name": "payment", "type": "uint256"}, - { - "internalType": "address payable", - "name": "paymentReceiver", - "type": "address", - }, - ], - "name": "setup", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], - "name": "signedMessages", - "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], - "stateMutability": "view", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "targetContract", "type": "address"}, - {"internalType": "bytes", "name": "calldataPayload", "type": "bytes"}, - ], - "name": "simulateAndRevert", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "prevOwner", "type": "address"}, - {"internalType": "address", "name": "oldOwner", "type": "address"}, - {"internalType": "address", "name": "newOwner", "type": "address"}, - ], - "name": "swapOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", - }, - {"stateMutability": "payable", "type": "receive"}, -] - -proxy_factory_v1_3_0_abi = [ - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address", - }, - { - "indexed": False, - "internalType": "address", - "name": "singleton", - "type": "address", - }, - ], - "name": "ProxyCreation", - "type": "event", - }, - { - "inputs": [ - {"internalType": "address", "name": "_singleton", "type": "address"}, - {"internalType": "bytes", "name": "initializer", "type": "bytes"}, - {"internalType": "uint256", "name": "saltNonce", "type": "uint256"}, - ], - "name": "calculateCreateProxyWithNonceAddress", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address", - } - ], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "singleton", "type": "address"}, - {"internalType": "bytes", "name": "data", "type": "bytes"}, - ], - "name": "createProxy", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address", - } - ], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "_singleton", "type": "address"}, - {"internalType": "bytes", "name": "initializer", "type": "bytes"}, - {"internalType": "uint256", "name": "saltNonce", "type": "uint256"}, - { - "internalType": "contract IProxyCreationCallback", - "name": "callback", - "type": "address", - }, - ], - "name": "createProxyWithCallback", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address", - } - ], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [ - {"internalType": "address", "name": "_singleton", "type": "address"}, - {"internalType": "bytes", "name": "initializer", "type": "bytes"}, - {"internalType": "uint256", "name": "saltNonce", "type": "uint256"}, - ], - "name": "createProxyWithNonce", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address", - } - ], - "stateMutability": "nonpayable", - "type": "function", - }, - { - "inputs": [], - "name": "proxyCreationCode", - "outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}], - "stateMutability": "pure", - "type": "function", - }, - { - "inputs": [], - "name": "proxyRuntimeCode", - "outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}], - "stateMutability": "pure", - "type": "function", - }, -] diff --git a/safe_transaction_service/history/indexers/events_indexer.py b/safe_transaction_service/history/indexers/events_indexer.py index 7e5a24036..406894522 100644 --- a/safe_transaction_service/history/indexers/events_indexer.py +++ b/safe_transaction_service/history/indexers/events_indexer.py @@ -220,6 +220,7 @@ def decode_element(self, log_receipt: LogReceipt) -> Optional[EventData]: """ for event_to_listen in self.events_to_listen[log_receipt["topics"][0].hex()]: # Try to decode using all the existing ABIs + # One topic can have multiple matching ABIs due to `indexed` elements changing how to decode it try: return event_to_listen.process_log(log_receipt) except LogTopicError: diff --git a/safe_transaction_service/history/indexers/proxy_factory_indexer.py b/safe_transaction_service/history/indexers/proxy_factory_indexer.py index 3446c34db..d7c1dcbdd 100644 --- a/safe_transaction_service/history/indexers/proxy_factory_indexer.py +++ b/safe_transaction_service/history/indexers/proxy_factory_indexer.py @@ -8,8 +8,9 @@ from gnosis.eth import EthereumClient from gnosis.eth.constants import NULL_ADDRESS from gnosis.eth.contracts import ( - get_proxy_factory_contract, get_proxy_factory_V1_1_1_contract, + get_proxy_factory_V1_3_0_contract, + get_proxy_factory_V1_4_1_contract, ) from ..models import ProxyFactory, SafeContract @@ -40,13 +41,22 @@ def del_singleton(cls): class ProxyFactoryIndexer(EventsIndexer): @cached_property def contract_events(self) -> List[ContractEvent]: - old_proxy_factory_contract = get_proxy_factory_V1_1_1_contract( + proxy_factory_v1_1_1_contract = get_proxy_factory_V1_1_1_contract( + self.ethereum_client.w3 + ) + proxy_factory_v1_3_0_contract = get_proxy_factory_V1_3_0_contract( + self.ethereum_client.w3 + ) + proxy_factory_v_1_4_1_contract = get_proxy_factory_V1_4_1_contract( self.ethereum_client.w3 ) - proxy_factory_contract = get_proxy_factory_contract(self.ethereum_client.w3) return [ - old_proxy_factory_contract.events.ProxyCreation(), - proxy_factory_contract.events.ProxyCreation(), + # event ProxyCreation(Proxy proxy) + proxy_factory_v1_1_1_contract.events.ProxyCreation(), + # event ProxyCreation(GnosisSafeProxy proxy, address singleton) + proxy_factory_v1_3_0_contract.events.ProxyCreation(), + # event ProxyCreation(SafeProxy indexed proxy, address singleton) + proxy_factory_v_1_4_1_contract.events.ProxyCreation(), ] @property diff --git a/safe_transaction_service/history/indexers/safe_events_indexer.py b/safe_transaction_service/history/indexers/safe_events_indexer.py index d51731e52..428881ac1 100644 --- a/safe_transaction_service/history/indexers/safe_events_indexer.py +++ b/safe_transaction_service/history/indexers/safe_events_indexer.py @@ -12,7 +12,13 @@ from gnosis.eth import EthereumClient from gnosis.eth.constants import NULL_ADDRESS -from gnosis.eth.contracts import get_safe_V1_1_1_contract +from gnosis.eth.contracts import ( + get_proxy_factory_V1_3_0_contract, + get_proxy_factory_V1_4_1_contract, + get_safe_V1_1_1_contract, + get_safe_V1_3_0_contract, + get_safe_V1_4_1_contract, +) from ..models import ( EthereumBlock, @@ -22,7 +28,6 @@ InternalTxType, SafeMasterCopy, ) -from .abis.gnosis import gnosis_safe_l2_v1_3_0_abi, proxy_factory_v1_3_0_abi from .events_indexer import EventsIndexer logger = getLogger(__name__) @@ -102,10 +107,12 @@ def contract_events(self) -> List[ContractEvent]: ); event ExecutionFailure( - bytes32 txHash, uint256 payment + bytes32 txHash, + uint256 payment ); event ExecutionSuccess( - bytes32 txHash, uint256 payment + bytes32 txHash, + uint256 payment ); event EnabledModule(address module); @@ -129,46 +136,127 @@ def contract_events(self) -> List[ContractEvent]: # ProxyFactory event ProxyCreation(GnosisSafeProxy proxy, address singleton); - Safe v1.4.0 L2 Events + Safe v1.4.1 L2 Events ------------------ - TODO: Add them on on deployment + event SafeMultiSigTransaction( + address to, + uint256 value, + bytes data, + Enum.Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver, + bytes signatures, + // We combine nonce, sender and threshold into one to avoid stack too deep + // Dev note: additionalInfo should not contain `bytes`, as this complicates decoding + bytes additionalInfo + ); + + event SafeModuleTransaction( + address module, + address to, + uint256 value, + bytes data, + Enum.Operation operation, + ); + + event SafeSetup( + address indexed initiator, + address[] owners, + uint256 threshold, + address initializer, + address fallbackHandler + ); + + event ApproveHash( + bytes32 indexed approvedHash, + address indexed owner + ); + + event SignMsg( + bytes32 indexed msgHash + ); + + event ExecutionFailure( + bytes32 indexed txHash, + uint256 payment + ); + + event ExecutionSuccess( + bytes32 indexed txHash, + uint256 payment + ); + + event EnabledModule(address indexed module); + event DisabledModule(address indexed module); + event ExecutionFromModuleSuccess(address indexed module); + event ExecutionFromModuleFailure(address indexed module); + + event AddedOwner(address indexed owner); + event RemovedOwner(address indexed owner); + event ChangedThreshold(uint256 threshold); + + # Incoming Ether + event SafeReceived( + address indexed sender, + uint256 value + ); + + event ChangedFallbackHandler(address indexed handler); + event ChangedGuard(address indexed guard); + + # ProxyFactory + event ProxyCreation(GnosisSafeProxy indexed proxy, address singleton); :return: List of supported `ContractEvent` """ - l2_contract = self.ethereum_client.w3.eth.contract( - abi=gnosis_safe_l2_v1_3_0_abi + proxy_factory_v1_4_1_contract = get_proxy_factory_V1_4_1_contract( + self.ethereum_client.w3 ) - proxy_factory_contract = self.ethereum_client.w3.eth.contract( - abi=proxy_factory_v1_3_0_abi + proxy_factory_v1_3_0_contract = get_proxy_factory_V1_3_0_contract( + self.ethereum_client.w3 ) - old_contract = get_safe_V1_1_1_contract(self.ethereum_client.w3) + safe_l2_v1_4_1_contract = get_safe_V1_4_1_contract(self.ethereum_client.w3) + safe_l2_v1_3_0_contract = get_safe_V1_3_0_contract(self.ethereum_client.w3) + safe_v1_1_1_contract = get_safe_V1_1_1_contract(self.ethereum_client.w3) return [ - l2_contract.events.SafeMultiSigTransaction(), - l2_contract.events.SafeModuleTransaction(), - l2_contract.events.SafeSetup(), - l2_contract.events.ApproveHash(), - l2_contract.events.SignMsg(), - l2_contract.events.ExecutionFailure(), - l2_contract.events.ExecutionSuccess(), + safe_l2_v1_3_0_contract.events.SafeMultiSigTransaction(), + safe_l2_v1_3_0_contract.events.SafeModuleTransaction(), + safe_l2_v1_3_0_contract.events.SafeSetup(), + safe_l2_v1_3_0_contract.events.ApproveHash(), + safe_l2_v1_3_0_contract.events.SignMsg(), + safe_l2_v1_4_1_contract.events.ExecutionFailure(), + safe_l2_v1_3_0_contract.events.ExecutionFailure(), + safe_l2_v1_4_1_contract.events.ExecutionSuccess(), + safe_l2_v1_3_0_contract.events.ExecutionSuccess(), # Modules - l2_contract.events.EnabledModule(), - l2_contract.events.DisabledModule(), - l2_contract.events.ExecutionFromModuleSuccess(), - l2_contract.events.ExecutionFromModuleFailure(), + safe_l2_v1_4_1_contract.events.EnabledModule(), + safe_l2_v1_3_0_contract.events.EnabledModule(), + safe_l2_v1_4_1_contract.events.DisabledModule(), + safe_l2_v1_3_0_contract.events.DisabledModule(), + safe_l2_v1_3_0_contract.events.ExecutionFromModuleSuccess(), + safe_l2_v1_3_0_contract.events.ExecutionFromModuleFailure(), # Owners - l2_contract.events.AddedOwner(), - l2_contract.events.RemovedOwner(), - l2_contract.events.ChangedThreshold(), + safe_l2_v1_4_1_contract.events.AddedOwner(), + safe_l2_v1_3_0_contract.events.AddedOwner(), + safe_l2_v1_4_1_contract.events.RemovedOwner(), + safe_l2_v1_3_0_contract.events.RemovedOwner(), + safe_l2_v1_3_0_contract.events.ChangedThreshold(), # Incoming Ether - l2_contract.events.SafeReceived(), + safe_l2_v1_3_0_contract.events.SafeReceived(), # Changed FallbackHandler - l2_contract.events.ChangedFallbackHandler(), + safe_l2_v1_4_1_contract.events.ChangedFallbackHandler(), + safe_l2_v1_3_0_contract.events.ChangedFallbackHandler(), # Changed Guard - l2_contract.events.ChangedGuard(), + safe_l2_v1_4_1_contract.events.ChangedGuard(), + safe_l2_v1_3_0_contract.events.ChangedGuard(), # Change Master Copy - old_contract.events.ChangedMasterCopy(), + safe_v1_1_1_contract.events.ChangedMasterCopy(), # Proxy creation - proxy_factory_contract.events.ProxyCreation(), + proxy_factory_v1_4_1_contract.events.ProxyCreation(), + proxy_factory_v1_3_0_contract.events.ProxyCreation(), ] @property diff --git a/safe_transaction_service/history/indexers/tx_processor.py b/safe_transaction_service/history/indexers/tx_processor.py index e05b833d6..ad03ae92e 100644 --- a/safe_transaction_service/history/indexers/tx_processor.py +++ b/safe_transaction_service/history/indexers/tx_processor.py @@ -12,7 +12,11 @@ from gnosis.eth import EthereumClient, EthereumClientProvider from gnosis.eth.constants import NULL_ADDRESS -from gnosis.eth.contracts import get_safe_V1_0_0_contract, get_safe_V1_3_0_contract +from gnosis.eth.contracts import ( + get_safe_V1_0_0_contract, + get_safe_V1_3_0_contract, + get_safe_V1_4_1_contract, +) from gnosis.safe import SafeTx from gnosis.safe.safe_signature import SafeSignature, SafeSignatureApprovedHash @@ -101,9 +105,11 @@ def __init__( self.safe_tx_failure_events = [ get_safe_V1_0_0_contract(dummy_w3).events.ExecutionFailed(), get_safe_V1_3_0_contract(dummy_w3).events.ExecutionFailure(), + get_safe_V1_4_1_contract(dummy_w3).events.ExecutionFailure(), ] self.safe_tx_module_failure_events = [ - get_safe_V1_3_0_contract(dummy_w3).events.ExecutionFromModuleFailure() + get_safe_V1_3_0_contract(dummy_w3).events.ExecutionFromModuleFailure(), + get_safe_V1_4_1_contract(dummy_w3).events.ExecutionFromModuleFailure(), ] self.safe_tx_failure_events_topics = { @@ -131,40 +137,54 @@ def is_failed( ) -> bool: """ Detects failure events on a Safe Multisig Tx + :param ethereum_tx: :param safe_tx_hash: :return: True if a Multisig Transaction is failed, False otherwise """ - # TODO Refactor this function to `Safe` in gnosis-py, it doesn't belong here - safe_tx_hash = HexBytes(safe_tx_hash).hex() + # TODO Refactor this function to `Safe` in safe-eth-py, it doesn't belong here + safe_tx_hash = HexBytes(safe_tx_hash) for log in ethereum_tx.logs: if ( log["topics"] and log["data"] and HexBytes(log["topics"][0]) in self.safe_tx_failure_events_topics - and log["data"][:66] == safe_tx_hash - ): # 66 is the beginning of the event data, the rest is payment - return True + ): + if ( + len(log["topics"]) == 2 + and HexBytes(log["topics"][1]) == safe_tx_hash + ): + # On v1.4.1 safe_tx_hash is indexed, so it will be topic[1] + # event ExecutionFailure(bytes32 indexed txHash, uint256 payment); + return True + elif HexBytes(log["data"])[:32] == safe_tx_hash: + # On v1.3.0 safe_tx_hash was not indexed, it was stored in the first 32 bytes, the rest is payment + # event ExecutionFailure(bytes32 txHash, uint256 payment); + return True return False def is_module_failed( - self, ethereum_tx: EthereumTx, module_address: str, safe_address: str + self, + ethereum_tx: EthereumTx, + module_address: ChecksumAddress, + safe_address: ChecksumAddress, ) -> bool: """ Detects module failure events on a Safe Module Tx + :param ethereum_tx: :param module_address: :param safe_address: :return: True if a Module Transaction is failed, False otherwise """ - # TODO Refactor this function to `Safe` in gnosis-py, it doesn't belong here + # TODO Refactor this function to `Safe` in safe-eth-py, it doesn't belong here for log in ethereum_tx.logs: if ( len(log["topics"]) == 2 and (log["address"] == safe_address if "address" in log else True) and HexBytes(log["topics"][0]) in self.safe_tx_module_failure_topics and HexBytes(log["topics"][1])[-20:] - == HexBytes(module_address) # 20 address size in bytes + == HexBytes(module_address) # 20 bytes is an address size ): return True return False diff --git a/safe_transaction_service/history/serializers.py b/safe_transaction_service/history/serializers.py index 7c88d2379..27135249f 100644 --- a/safe_transaction_service/history/serializers.py +++ b/safe_transaction_service/history/serializers.py @@ -163,7 +163,6 @@ def validate(self, attrs): attrs["gas_token"], attrs["refund_receiver"], safe_nonce=attrs["nonce"], - safe_version=safe_version, ) contract_transaction_hash = safe_tx.safe_tx_hash diff --git a/safe_transaction_service/history/services/safe_service.py b/safe_transaction_service/history/services/safe_service.py index 1d4698960..9caeac01d 100644 --- a/safe_transaction_service/history/services/safe_service.py +++ b/safe_transaction_service/history/services/safe_service.py @@ -7,7 +7,11 @@ from web3 import Web3 from gnosis.eth import EthereumClient, EthereumClientProvider -from gnosis.eth.contracts import get_cpk_factory_contract, get_proxy_factory_contract +from gnosis.eth.contracts import ( + get_cpk_factory_contract, + get_proxy_factory_V1_3_0_contract, + get_proxy_factory_V1_4_1_contract, +) from gnosis.safe import Safe from gnosis.safe.exceptions import CannotRetrieveSafeInfoException from gnosis.safe.safe import SafeInfo @@ -77,7 +81,8 @@ def __init__( self.ethereum_client = ethereum_client self.ethereum_tracing_client = ethereum_tracing_client dummy_w3 = Web3() # Not needed, just used to decode contracts - self.proxy_factory_contract = get_proxy_factory_contract(dummy_w3) + self.proxy_factory_v1_4_1_contract = get_proxy_factory_V1_4_1_contract(dummy_w3) + self.proxy_factory_v1_3_0_contract = get_proxy_factory_V1_3_0_contract(dummy_w3) self.cpk_proxy_factory_contract = get_cpk_factory_contract(dummy_w3) def get_safe_creation_info(self, safe_address: str) -> Optional[SafeCreationInfo]: @@ -194,23 +199,29 @@ def _decode_proxy_factory( if not data: return None try: - _, data_decoded = self.proxy_factory_contract.decode_function_input(data) - master_copy = ( - data_decoded.get("masterCopy") - or data_decoded.get("_mastercopy") - or data_decoded.get("_singleton") - or data_decoded.get("singleton") - ) - setup_data = data_decoded.get("data") or data_decoded.get("initializer") - if master_copy and setup_data is not None: - return master_copy, setup_data - - logger.error( - "Problem decoding proxy factory, data_decoded=%s", data_decoded + _, data_decoded = self.proxy_factory_v1_3_0_contract.decode_function_input( + data ) - return None except ValueError: - return None + try: + ( + _, + data_decoded, + ) = self.proxy_factory_v1_4_1_contract.decode_function_input(data) + except ValueError: + return None + master_copy = ( + data_decoded.get("masterCopy") + or data_decoded.get("_mastercopy") + or data_decoded.get("_singleton") + or data_decoded.get("singleton") + ) + setup_data = data_decoded.get("data") or data_decoded.get("initializer") + if master_copy and setup_data is not None: + return master_copy, setup_data + + logger.error("Problem decoding proxy factory, data_decoded=%s", data_decoded) + return None def _decode_cpk_proxy_factory( self, data: Union[bytes, str] diff --git a/safe_transaction_service/history/tests/test_balance_service.py b/safe_transaction_service/history/tests/test_balance_service.py index 7c8bd3ef6..49a106f37 100644 --- a/safe_transaction_service/history/tests/test_balance_service.py +++ b/safe_transaction_service/history/tests/test_balance_service.py @@ -67,7 +67,14 @@ def test_get_usd_balances( self.assertEqual(balances[0].balance, value) tokens_value = int(12 * 1e18) - erc20 = deploy_erc20(self.w3, "Eurodollar", "EUD", safe_address, tokens_value) + erc20 = deploy_erc20( + self.w3, + self.ethereum_test_account, + "Eurodollar", + "EUD", + safe_address, + tokens_value, + ) balances = balance_service.get_usd_balances(safe_address) self.assertEqual(len(balances), 1) @@ -123,10 +130,22 @@ def test_get_usd_balances( ) # Test sorting - erc20_2 = deploy_erc20(self.w3, "Peseta", "PTA", safe_address, tokens_value) + erc20_2 = deploy_erc20( + self.w3, + self.ethereum_test_account, + "Peseta", + "PTA", + safe_address, + tokens_value, + ) token_info_2 = balance_service.get_token_info(erc20_2.address) erc20_3 = deploy_erc20( - self.w3, "Double Dollars", "DD", safe_address, tokens_value + self.w3, + self.ethereum_test_account, + "Double Dollars", + "DD", + safe_address, + tokens_value, ) token_info_3 = balance_service.get_token_info(erc20_3.address) @@ -207,7 +226,12 @@ def test_get_usd_balances_copy_price( tokens_value = int(12 * 1e18) erc20 = deploy_erc20( - self.w3, "Galactic Credit Standard", "GCS", safe_address, tokens_value + self.w3, + self.ethereum_test_account, + "Galactic Credit Standard", + "GCS", + safe_address, + tokens_value, ) ERC20TransferFactory(address=erc20.address, to=safe_address) diff --git a/safe_transaction_service/history/tests/test_proxy_factory_indexer.py b/safe_transaction_service/history/tests/test_proxy_factory_indexer.py index e5edc9f7c..64df91c9b 100644 --- a/safe_transaction_service/history/tests/test_proxy_factory_indexer.py +++ b/safe_transaction_service/history/tests/test_proxy_factory_indexer.py @@ -13,8 +13,8 @@ def test_proxy_factory_indexer(self): proxy_factory_indexer.confirmations = 0 self.assertEqual(proxy_factory_indexer.start(), (0, 0)) ProxyFactoryFactory(address=self.proxy_factory.address) - ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract( - self.ethereum_test_account, self.safe_contract_address + ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract_with_nonce( + self.ethereum_test_account, self.safe_contract.address ) safe_contract_address = ethereum_tx_sent.contract_address self.w3.eth.wait_for_transaction_receipt(ethereum_tx_sent.tx_hash) diff --git a/safe_transaction_service/history/tests/test_safe_events_indexer.py b/safe_transaction_service/history/tests/test_safe_events_indexer.py index 2526dcd0f..42e72894f 100644 --- a/safe_transaction_service/history/tests/test_safe_events_indexer.py +++ b/safe_transaction_service/history/tests/test_safe_events_indexer.py @@ -1,12 +1,14 @@ from django.test import TestCase from eth_account import Account +from eth_typing import ChecksumAddress from hexbytes import HexBytes +from web3 import Web3 from web3.datastructures import AttributeDict from web3.types import LogReceipt from gnosis.eth.constants import NULL_ADDRESS, SENTINEL_ADDRESS -from gnosis.eth.contracts import get_safe_V1_3_0_contract +from gnosis.eth.contracts import get_safe_V1_3_0_contract, get_safe_V1_4_1_contract from gnosis.safe import Safe from gnosis.safe.tests.safe_test_case import SafeTestCaseMixin @@ -27,7 +29,7 @@ from .mocks.mocks_safe_events_indexer import safe_events_mock -class TestSafeEventsIndexer(SafeTestCaseMixin, TestCase): +class TestSafeEventsIndexerV1_4_1(SafeTestCaseMixin, TestCase): def setUp(self) -> None: self.safe_events_indexer = SafeEventsIndexer( self.ethereum_client, confirmations=0, blocks_to_reindex_again=0 @@ -37,6 +39,23 @@ def setUp(self) -> None: def tearDown(self) -> None: SafeEventsIndexerProvider.del_singleton() + @property + def safe_contract_version(self) -> str: + return "1.4.1" + + @property + def safe_contract(self): + """ + :return: Last Safe Contract available + """ + return self.safe_contract_V1_4_1 + + def get_safe_contract(self, w3: Web3, address: ChecksumAddress): + """ + :return: Last Safe Contract available + """ + return get_safe_V1_4_1_contract(w3, address=address) + def test_safe_events_indexer_provider(self): safe_events_indexer = SafeEventsIndexerProvider() self.assertEqual(safe_events_indexer.confirmations, 0) @@ -47,54 +66,69 @@ def test_safe_events_indexer_provider(self): def test_invalid_event(self): """ - AddedOwner event broke indexer on BSC. Same signature, but different number of indexed attributes + Events with same name and types, but different indexed elements can break the indexer + We will test the expected: + + event ExecutionSuccess( + bytes32 txHash, + uint256 payment + ); + + With the made out: + event ExecutionSuccess( + bytes32 indexed txHash, + uint256 indexed payment + ); """ valid_event: LogReceipt = AttributeDict( { - "address": "0x384f55D8BD4046461433A56bb87fe4aA615C0cc8", - "blockHash": HexBytes( - "0x551a6e5ca972c453873898be696980d7ff65d27a6f80ddffab17591144c99e01" - ), - "blockNumber": 9205844, - "data": "0x000000000000000000000000a1350318b2907ee0f6c8918eddc778a0b633e774", - "logIndex": 0, - "removed": False, + "address": "0xE618d8147210d45ffCBd2E3b33DD44252a43fF76", "topics": [ HexBytes( - "0x9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26" + "0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e" ) ], + "data": HexBytes( + "0x55e61223bfe56101c8243067945cf90da23f0e0a3409eac65dc6e8852833cf440000000000000000000000000000000000000000000000000000000000000000" + ), + "blockNumber": 9727973, "transactionHash": HexBytes( - "0x7e4b2bb0ac5129552908e9c8433ea1746f76616188e8c3597a6bdce88d0b474c" + "0x9afccb1cf5498ae564b5589bf4bbf0b29b486f52952d1270dd51702ed2e29ff9" ), - "transactionIndex": 0, - "transactionLogIndex": "0x0", - "type": "mined", + "transactionIndex": 50, + "blockHash": HexBytes( + "0x3b2a9816f9b4280dc0190f1aafb910c99efbbf836e1865ab068ecbf6c0402fa7" + ), + "logIndex": 129, + "removed": False, } ) dangling_event: LogReceipt = AttributeDict( { - "address": "0x1E44C806f1AfD4f420C10c8088f4e0388F066E7A", + "address": "0xE618d8147210d45ffCBd2E3b33DD44252a43fF76", "topics": [ HexBytes( - "0x9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26" + "0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e" ), HexBytes( - "0x00000000000000000000000020212521370dd2dde0b0e3ac25b65eb3e859d303" + "0x55e61223bfe56101c8243067945cf90da23f0e0a3409eac65dc6e8852833cf44" + ), + HexBytes( + "0x0000000000000000000000000000000000000000000000000000000000000000" ), ], - "data": "0x", - "blockNumber": 10129293, + "data": HexBytes("0x"), + "blockNumber": 9727973, "transactionHash": HexBytes( - "0xc19ef099702fb9f7d7962925428683eff534e009210ef2cf23135f43962c192a" + "0x9afccb1cf5498ae564b5589bf4bbf0b29b486f52952d1270dd51702ed2e29ff9" ), - "transactionIndex": 89, + "transactionIndex": 50, "blockHash": HexBytes( - "0x6b41eac9177a1606e1a853adf3f3da018fcf476f7d217acb69b7d130bdfaf2c9" + "0x3b2a9816f9b4280dc0190f1aafb910c99efbbf836e1865ab068ecbf6c0402fa7" ), - "logIndex": 290, + "logIndex": 129, "removed": False, } ) @@ -112,23 +146,26 @@ def test_invalid_event(self): valid_event["topics"][0].hex(), self.safe_events_indexer.events_to_listen ) - # Dangling event cannot be decoded + # Dangling event cannot be decoded, but valid event is expected_event = AttributeDict( { "args": AttributeDict( - {"owner": "0xa1350318b2907ee0f6c8918edDC778A0b633e774"} + { + "txHash": b"U\xe6\x12#\xbf\xe5a\x01\xc8$0g\x94\\\xf9\r\xa2?\x0e\n4\t\xea\xc6]\xc6\xe8\x85(3\xcfD", + "payment": 0, + } ), - "event": "AddedOwner", - "logIndex": 0, - "transactionIndex": 0, + "event": "ExecutionSuccess", + "logIndex": 129, + "transactionIndex": 50, "transactionHash": HexBytes( - "0x7e4b2bb0ac5129552908e9c8433ea1746f76616188e8c3597a6bdce88d0b474c" + "0x9afccb1cf5498ae564b5589bf4bbf0b29b486f52952d1270dd51702ed2e29ff9" ), - "address": "0x384f55D8BD4046461433A56bb87fe4aA615C0cc8", + "address": "0xE618d8147210d45ffCBd2E3b33DD44252a43fF76", "blockHash": HexBytes( - "0x551a6e5ca972c453873898be696980d7ff65d27a6f80ddffab17591144c99e01" + "0x3b2a9816f9b4280dc0190f1aafb910c99efbbf836e1865ab068ecbf6c0402fa7" ), - "blockNumber": 9205844, + "blockNumber": 9727973, } ) self.assertEqual( @@ -162,18 +199,20 @@ def test_safe_events_indexer(self): address=self.safe_contract.address, initial_block_number=initial_block_number, tx_block_number=initial_block_number, - version="1.3.0", + version=self.safe_contract_version, l2=True, ) - ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract( + ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract_with_nonce( self.ethereum_test_account, self.safe_contract.address, initializer=initializer, ) safe_address = ethereum_tx_sent.contract_address safe = Safe(safe_address, self.ethereum_client) - safe_contract = get_safe_V1_3_0_contract(self.w3, safe_address) - self.assertEqual(safe_contract.functions.VERSION().call(), "1.3.0") + safe_contract = self.get_safe_contract(self.w3, safe_address) + self.assertEqual( + safe_contract.functions.VERSION().call(), self.safe_contract_version + ) self.assertEqual(InternalTx.objects.count(), 0) self.assertEqual(InternalTxDecoded.objects.count(), 0) @@ -520,8 +559,8 @@ def test_safe_events_indexer(self): ) self.assertEqual(MultisigConfirmation.objects.count(), 9) - # Set guard (nonce: 7) INVALIDATES SAFE, as no more transactions can be done --------------------------------- - guard_address = Account.create().address + # Set guard (nonce: 7) --------------------------------- + guard_address = self.deploy_example_guard() data = HexBytes( self.safe_contract.functions.setGuard(guard_address).build_transaction( {"gas": 1, "gasPrice": 1} @@ -532,7 +571,8 @@ def test_safe_events_indexer(self): multisig_tx.sign(owner_account_1.key) multisig_tx.execute(self.ethereum_test_account.key) # Process events: SafeMultiSigTransaction, ChangedGuard, ExecutionSuccess - self.assertEqual(self.safe_events_indexer.start(), (3, 1)) + # 2 blocks will be processed due to the guard deployment + self.assertEqual(self.safe_events_indexer.start(), (3, 2)) self.safe_tx_processor.process_decoded_transactions(txs_decoded_queryset.all()) # Add one SafeStatus increasing the nonce and another one changing the guard self.assertEqual(SafeStatus.objects.count(), 17) @@ -666,3 +706,22 @@ def test_auto_adjust_block_limit(self): with self.safe_events_indexer.auto_adjust_block_limit(100, 104): pass self.assertEqual(self.safe_events_indexer.block_process_limit, 5) + + +class TestSafeEventsIndexerV1_3_0(TestSafeEventsIndexerV1_4_1): + @property + def safe_contract_version(self) -> str: + return "1.3.0" + + @property + def safe_contract(self): + """ + :return: Last Safe Contract available + """ + return self.safe_contract_V1_3_0 + + def get_safe_contract(self, w3: Web3, address: ChecksumAddress): + """ + :return: Last Safe Contract available + """ + return get_safe_V1_3_0_contract(w3, address=address) diff --git a/safe_transaction_service/history/tests/test_tx_processor.py b/safe_transaction_service/history/tests/test_tx_processor.py index 4a6235498..ec76fc11b 100644 --- a/safe_transaction_service/history/tests/test_tx_processor.py +++ b/safe_transaction_service/history/tests/test_tx_processor.py @@ -326,7 +326,7 @@ def test_tx_processor_with_factory(self): SafeSignatureType.APPROVED_HASH.value, ) - def test_tx_processor_failed(self): + def test_tx_processor_is_failed(self): tx_processor = self.tx_processor # Event for Safes < 1.1.1 logs = [ @@ -362,6 +362,25 @@ def test_tx_processor_failed(self): tx_processor.is_failed(ethereum_tx, Web3.keccak(text="hola").hex()) ) + # Event for Safes >= 1.4.1 + safe_tx_hash = ( + "0x4c15b21b9c3b57aebba3c274bf0a437950bd0eea46bc7a7b2df892f91f720311" + ) + logs = [ + { + "data": "0000000000000000000000000000000000000000000000000000000000000000", + "topics": [ + "0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23", + "0x4c15b21b9c3b57aebba3c274bf0a437950bd0eea46bc7a7b2df892f91f720311", + ], + } + ] + ethereum_tx = EthereumTxFactory(logs=logs) + self.assertTrue(tx_processor.is_failed(ethereum_tx, safe_tx_hash)) + self.assertFalse( + tx_processor.is_failed(ethereum_tx, Web3.keccak(text="hola").hex()) + ) + def test_tx_is_version_breaking_signatures(self): tx_processor = self.tx_processor self.assertTrue(tx_processor.is_version_breaking_signatures("0.0.1", "1.1.1")) diff --git a/safe_transaction_service/history/tests/test_views.py b/safe_transaction_service/history/tests/test_views.py index ea19d08a5..1297832b8 100644 --- a/safe_transaction_service/history/tests/test_views.py +++ b/safe_transaction_service/history/tests/test_views.py @@ -2900,12 +2900,12 @@ def test_safe_creation_view(self): ): # `another_trace_2` should change the `creator` and `master_copy` and `setup_data` should appear # Taken from rinkeby - create_test_data = { + create_test_data_v1_0_0 = { "master_copy": "0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A", "setup_data": "0xa97ab18a00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006e45d69a383ceca3d54688e833bd0e1388747e6b00000000000000000000000061a0c717d18232711bc788f19c9cd56a43cc88720000000000000000000000007724b234c9099c205f03b458944942bceba134080000000000000000000000000000000000000000000000000000000000000000", "data": "0x61b69abd000000000000000000000000b6029ea3b2c51d09a50b53ca8012feeb05bda35a00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000184a97ab18a00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006e45d69a383ceca3d54688e833bd0e1388747e6b00000000000000000000000061a0c717d18232711bc788f19c9cd56a43cc88720000000000000000000000007724b234c9099c205f03b458944942bceba13408000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", } - data_decoded_1 = { + data_decoded_v1_0_0 = { "method": "setup", "parameters": [ { @@ -2938,12 +2938,12 @@ def test_safe_creation_view(self): ], } - create_test_data_2 = { + create_test_data_v1_1_1 = { "master_copy": "0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F", "setup_data": "0xb63e800d0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ac9b6dd409ff10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000085c26101f353f38e45c72d414b44972831f07be3000000000000000000000000235518798770d7336c5c4908dd1019457fea43a10000000000000000000000007f63c25665ea7e85500eaeb806e552e651b07b9d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "data": "0x1688f0b900000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000002cecc9e861200000000000000000000000000000000000000000000000000000000000001c4b63e800d0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ac9b6dd409ff10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000085c26101f353f38e45c72d414b44972831f07be3000000000000000000000000235518798770d7336c5c4908dd1019457fea43a10000000000000000000000007f63c25665ea7e85500eaeb806e552e651b07b9d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", } - data_decoded_2 = { + data_decoded_v1_1_1 = { "method": "setup", "parameters": [ { @@ -2988,34 +2988,79 @@ def test_safe_creation_view(self): } data_decoded_cpk = None + # Using `createProxyWithNonce` for v1.4.1, example taken from Goerli + create_v1_4_1_test_data = { + "master_copy": "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762", + "setup_data": "0xb63e800d0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c7d289db6238596b5a5dbe2f1df9d29c930f959c00000000000000000000000068bbf2084546ccba3cf2f604736e77b3b2a671600000000000000000000000000000000000000000000000000000000000000000", + "data": "0x1688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000018a765221620000000000000000000000000000000000000000000000000000000000000184b63e800d0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c7d289db6238596b5a5dbe2f1df9d29c930f959c00000000000000000000000068bbf2084546ccba3cf2f604736e77b3b2a67160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + } + data_decoded_v1_4_1 = { + "method": "setup", + "parameters": [ + { + "name": "_owners", + "type": "address[]", + "value": [ + "0xC7D289DB6238596B5A5DBE2f1dF9D29C930F959c", + "0x68bbF2084546ccBA3Cf2F604736e77b3b2a67160", + ], + }, + {"name": "_threshold", "type": "uint256", "value": "2"}, + { + "name": "to", + "type": "address", + "value": "0x0000000000000000000000000000000000000000", + }, + {"name": "data", "type": "bytes", "value": "0x"}, + { + "name": "fallbackHandler", + "type": "address", + "value": "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99", + }, + { + "name": "paymentToken", + "type": "address", + "value": "0x0000000000000000000000000000000000000000", + }, + {"name": "payment", "type": "uint256", "value": "0"}, + { + "name": "paymentReceiver", + "type": "address", + "value": "0x0000000000000000000000000000000000000000", + }, + ], + } + for test_data, data_decoded in [ - (create_test_data, data_decoded_1), - (create_test_data_2, data_decoded_2), + (create_test_data_v1_0_0, data_decoded_v1_0_0), + (create_test_data_v1_1_1, data_decoded_v1_1_1), (create_cpk_test_data, data_decoded_cpk), + (create_v1_4_1_test_data, data_decoded_v1_4_1), ]: - another_trace_2["action"]["input"] = HexBytes(test_data["data"]) - response = self.client.get( - reverse("v1:history:safe-creation", args=(owner_address,)), - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - created_iso = ( - internal_tx.ethereum_tx.block.timestamp.isoformat().replace( - "+00:00", "Z" + with self.subTest(test_data=test_data, data_decoded=data_decoded): + another_trace_2["action"]["input"] = HexBytes(test_data["data"]) + response = self.client.get( + reverse("v1:history:safe-creation", args=(owner_address,)), + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + created_iso = ( + internal_tx.ethereum_tx.block.timestamp.isoformat().replace( + "+00:00", "Z" + ) + ) + self.assertEqual( + response.data, + { + "created": created_iso, + "creator": another_trace_2["action"]["from"], + "transaction_hash": internal_tx.ethereum_tx_id, + "factory_address": internal_tx._from, + "master_copy": test_data["master_copy"], + "setup_data": test_data["setup_data"], + "data_decoded": data_decoded, + }, ) - ) - self.assertEqual( - response.data, - { - "created": created_iso, - "creator": another_trace_2["action"]["from"], - "transaction_hash": internal_tx.ethereum_tx_id, - "factory_address": internal_tx._from, - "master_copy": test_data["master_copy"], - "setup_data": test_data["setup_data"], - "data_decoded": data_decoded, - }, - ) def test_safe_info_view(self): invalid_address = "0x2A" diff --git a/safe_transaction_service/safe_messages/tests/test_models.py b/safe_transaction_service/safe_messages/tests/test_models.py index 02b302196..ddc311004 100644 --- a/safe_transaction_service/safe_messages/tests/test_models.py +++ b/safe_transaction_service/safe_messages/tests/test_models.py @@ -35,7 +35,7 @@ def test_str(self): ]: with self.subTest(input=input): with mock.patch( - "gnosis.safe.Safe.domain_separator", + "gnosis.safe.safe.Safe.domain_separator", return_value=mock_domain_separator, new_callable=PropertyMock, ): diff --git a/safe_transaction_service/tokens/tests/test_commands.py b/safe_transaction_service/tokens/tests/test_commands.py index 9bb5654ae..b6b487fbe 100644 --- a/safe_transaction_service/tokens/tests/test_commands.py +++ b/safe_transaction_service/tokens/tests/test_commands.py @@ -7,9 +7,9 @@ from eth_account import Account -from gnosis.eth import EthereumClientProvider from gnosis.eth.ethereum_client import Erc20Info, Erc20Manager -from gnosis.eth.tests.utils import deploy_example_erc20 +from gnosis.eth.tests.ethereum_test_case import EthereumTestCaseMixin +from gnosis.eth.tests.utils import deploy_erc20 from ..clients import CoinMarketCapClient, CoinMarketCapToken from ..models import Token @@ -51,7 +51,7 @@ ] -class TestCommands(TestCase): +class TestCommands(EthereumTestCaseMixin, TestCase): def test_add_token(self): command = "add_token" buf = StringIO() @@ -63,8 +63,14 @@ def test_add_token(self): token.refresh_from_db() self.assertTrue(token.trusted) - ethereum_client = EthereumClientProvider() - erc20 = deploy_example_erc20(ethereum_client.w3, 10, Account.create().address) + erc20 = deploy_erc20( + self.ethereum_client.w3, + self.ethereum_test_account, + "Uxio", + "UXI", + Account.create().address, + 10, + ) call_command(command, erc20.address, "--no-prompt", stdout=buf) self.assertIn("Created token", buf.getvalue()) self.assertTrue(Token.objects.get(address=erc20.address).trusted)