class SafeStatus(models.Model): objects = SafeStatusManager.from_queryset(SafeStatusQuerySet)() internal_tx = models.OneToOneField(InternalTx, on_delete=models.CASCADE, related_name='safe_status', primary_key=True) address = EthereumAddressField(db_index=True) owners = ArrayField(EthereumAddressField()) threshold = Uint256Field() nonce = Uint256Field(default=0) master_copy = EthereumAddressField() fallback_handler = EthereumAddressField() enabled_modules = ArrayField(EthereumAddressField(), default=list) class Meta: unique_together = (('internal_tx', 'address'),) verbose_name_plural = 'Safe statuses' def __str__(self): return f'safe={self.address} threshold={self.threshold} owners={self.owners} nonce={self.nonce}' @property def block_number(self): return self.internal_tx.ethereum_tx.block_id def store_new(self, internal_tx: InternalTx) -> None: self.internal_tx = internal_tx return self.save()
class InternalTx(models.Model): objects = InternalTxManager() ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='internal_txs') _from = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT it can be null gas = Uint256Field() data = models.BinaryField(null=True) # `input` for Call, `init` for Create to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() gas_used = Uint256Field() contract_address = EthereumAddressField(null=True, db_index=True) # Create code = models.BinaryField(null=True) # Create output = models.BinaryField(null=True) # Call refund_address = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT tx_type = models.PositiveSmallIntegerField(choices=[(tag.value, tag.name) for tag in EthereumTxType]) call_type = models.PositiveSmallIntegerField(null=True, choices=[(tag.value, tag.name) for tag in EthereumTxCallType]) # Call trace_address = models.CharField(max_length=100) # Stringified traceAddress error = models.CharField(max_length=100, null=True) class Meta: unique_together = (('ethereum_tx', 'trace_address'),) def __str__(self): if self.to: return 'Internal tx hash={} from={} to={}'.format(self.ethereum_tx.tx_hash, self._from, self.to) else: return 'Internal tx hash={} from={}'.format(self.ethereum_tx.tx_hash, self._from)
class SafeMultisigTx(TimeStampedModel): objects = SafeMultisigTxManager.from_queryset(SafeMultisigTxQuerySet)() safe = models.ForeignKey(SafeContract, on_delete=models.CASCADE, related_name='multisig_txs') ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='multisig_txs') to = EthereumAddressField(null=True, blank=True, db_index=True) value = Uint256Field() data = models.BinaryField(null=True, blank=True) operation = models.PositiveSmallIntegerField( choices=[(tag.value, tag.name) for tag in SafeOperation]) safe_tx_gas = Uint256Field() data_gas = Uint256Field() gas_price = Uint256Field() gas_token = EthereumAddressField(null=True, blank=True) refund_receiver = EthereumAddressField(null=True, blank=True) signatures = models.BinaryField() nonce = Uint256Field() safe_tx_hash = Sha3HashField(unique=True, null=True, blank=True) def __str__(self): return '{} - {} - Safe {}'.format(self.ethereum_tx.tx_hash, SafeOperation(self.operation).name, self.safe.address) def get_safe_tx(self, ethereum_client: Optional[EthereumClient] = None ) -> SafeTx: return SafeTx( ethereum_client, self.safe_id, self.to, self.value, self.data.tobytes() if self.data else b'', self.operation, self.safe_tx_gas, self.data_gas, self.gas_price, self.gas_token, self.refund_receiver, signatures=self.signatures.tobytes() if self.signatures else b'', safe_nonce=self.nonce) def refund_benefit(self) -> Optional[int]: """ :return: Difference of the calculated payment fee and the actual executed payment fee. It will be `None` if transaction was not mined yet or if a `gas_token` was used (not easy to calculate the ether conversion at that point) """ if self.ethereum_tx_id and (not self.gas_token or self.gas_token == NULL_ADDRESS ) and self.ethereum_tx.gas_used: payment_fee = min(self.gas_price, self.ethereum_tx.gas_price) executed_fee = self.ethereum_tx.gas_used * self.ethereum_tx.gas_price return payment_fee - executed_fee def signers(self) -> List[str]: return self.get_safe_tx().signers
class SafeMultisigTx(TimeStampedModel): objects = SafeMultisigTxManager.from_queryset(SafeMultisigTxQuerySet)() safe = models.ForeignKey(SafeContract, on_delete=models.CASCADE, related_name='multisig_txs') ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='multisig_txs') to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() data = models.BinaryField(null=True) operation = models.PositiveSmallIntegerField(choices=[(tag.value, tag.name) for tag in SafeOperation]) safe_tx_gas = Uint256Field() data_gas = Uint256Field() gas_price = Uint256Field() gas_token = EthereumAddressField(null=True) refund_receiver = EthereumAddressField(null=True) signatures = models.BinaryField() nonce = Uint256Field() safe_tx_hash = Sha3HashField(unique=True, null=True) class Meta: unique_together = (('safe', 'nonce'),) def __str__(self): return '{} - {} - Safe {}'.format(self.ethereum_tx.tx_hash, SafeOperation(self.operation).name, self.safe.address) def get_safe_tx(self, ethereum_client: Optional[EthereumClient] = None) -> SafeTx: return SafeTx(ethereum_client, self.safe_id, self.to, self.value, self.data.tobytes() if self.data else b'', self.operation, self.safe_tx_gas, self.data_gas, self.gas_price, self.gas_token, self.refund_receiver, signatures=self.signatures.tobytes() if self.signatures else b'', safe_nonce=self.nonce)
class EthereumTx(TimeStampedModel): objects = EthereumTxManager() block = models.ForeignKey(EthereumBlock, on_delete=models.CASCADE, null=True, default=None, related_name='txs') # If mined tx_hash = Sha3HashField(unique=True, primary_key=True) gas_used = Uint256Field(null=True, default=None) # If mined status = models.IntegerField( null=True, default=None, db_index=True) # If mined. Old txs don't have `status` transaction_index = models.PositiveIntegerField(null=True, default=None) # If mined _from = EthereumAddressField(null=True, db_index=True) gas = Uint256Field() gas_price = Uint256Field() data = models.BinaryField(null=True) nonce = Uint256Field() to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() def __str__(self): return '{} status={} from={} to={}'.format(self.tx_hash, self.status, self._from, self.to) @property def success(self) -> Optional[bool]: if self.status is not None: return self.status == 1
class MultisigTransaction(TimeStampedModel): objects = MultisigTransactionManager.from_queryset( MultisigTransactionQuerySet)() safe_tx_hash = Sha3HashField(primary_key=True) safe = EthereumAddressField(db_index=True) ethereum_tx = models.ForeignKey(EthereumTx, null=True, default=None, blank=True, on_delete=models.SET_NULL, related_name='multisig_txs') to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() data = models.BinaryField(null=True) operation = models.PositiveSmallIntegerField( choices=[(tag.value, tag.name) for tag in SafeOperation]) safe_tx_gas = Uint256Field() base_gas = Uint256Field() gas_price = Uint256Field() gas_token = EthereumAddressField(null=True) refund_receiver = EthereumAddressField(null=True) signatures = models.BinaryField(null=True) # When tx is executed nonce = Uint256Field(db_index=True) failed = models.BooleanField(null=True, default=None, db_index=True) origin = models.CharField( null=True, default=None, max_length=200) # To store arbitrary data on the tx trusted = models.BooleanField( default=False, db_index=True) # Txs proposed by a delegate or with one confirmation def __str__(self): return f'{self.safe} - {self.nonce} - {self.safe_tx_hash}' @property def execution_date(self) -> Optional[datetime.datetime]: if self.ethereum_tx_id and self.ethereum_tx.block_id is not None: return self.ethereum_tx.block.timestamp return None @property def executed(self) -> bool: return bool(self.ethereum_tx_id and (self.ethereum_tx.block_id is not None)) @property def owners(self) -> Optional[List[str]]: if not self.signatures: return None else: signatures = bytes(self.signatures) safe_signatures = SafeSignature.parse_signature( signatures, self.safe_tx_hash) return [safe_signature.owner for safe_signature in safe_signatures]
class EthereumTx(TimeStampedModel): objects = EthereumTxManager() block = models.ForeignKey(EthereumBlock, on_delete=models.CASCADE, null=True, default=None, related_name='txs') # If mined tx_hash = Sha3HashField(primary_key=True) gas_used = Uint256Field(null=True, default=None) # If mined status = models.IntegerField( null=True, default=None, db_index=True) # If mined. Old txs don't have `status` logs = ArrayField(JSONField(), null=True, default=None) # If mined transaction_index = models.PositiveIntegerField(null=True, default=None) # If mined _from = EthereumAddressField(null=True, db_index=True) gas = Uint256Field() gas_price = Uint256Field() data = models.BinaryField(null=True) nonce = Uint256Field() to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() def __str__(self): return '{} status={} from={} to={}'.format(self.tx_hash, self.status, self._from, self.to) @property def execution_date(self) -> Optional[datetime.datetime]: if self.block_id is not None: return self.block.timestamp return None @property def success(self) -> Optional[bool]: if self.status is not None: return self.status == 1 def update_with_block_and_receipt(self, ethereum_block: 'EthereumBlock', tx_receipt: Dict[str, Any]): if self.block is None: self.block = ethereum_block self.gas_used = tx_receipt['gasUsed'] self.logs = [ clean_receipt_log(log) for log in tx_receipt.get('logs', list()) ] self.status = tx_receipt.get('status') self.transaction_index = tx_receipt['transactionIndex'] return self.save(update_fields=[ 'block', 'gas_used', 'logs', 'status', 'transaction_index' ])
class SafeCreation2(TimeStampedModel): objects = SafeCreationManager.from_queryset(SafeCreation2QuerySet)() safe = models.OneToOneField(SafeContract, on_delete=models.CASCADE, primary_key=True) master_copy = EthereumAddressField() proxy_factory = EthereumAddressField() salt_nonce = Uint256Field() owners = ArrayField(EthereumAddressField()) threshold = Uint256Field() # to = EthereumAddressField(null=True) # Contract address for optional delegate call # data = models.BinaryField(null=True) # Data payload for optional delegate call payment_token = EthereumAddressField(null=True) payment = Uint256Field() payment_receiver = EthereumAddressField( null=True) # If empty, `tx.origin` is used setup_data = models.BinaryField( null=True) # Binary data for safe `setup` call gas_estimated = Uint256Field() gas_price_estimated = Uint256Field() tx_hash = Sha3HashField(unique=True, null=True, default=None) block_number = models.IntegerField(null=True, default=None) # If mined class Meta: verbose_name_plural = "Safe creation2s" def __str__(self): if self.block_number: return 'Safe {} - Deployed on block number {}'.format( self.safe, self.block_number) else: return 'Safe {}'.format(self.safe) def deployed(self) -> bool: return self.block_number is not None def wei_estimated_deploy_cost(self) -> int: """ :return: int: Cost to deploy the contract in wei """ return self.gas_estimated * self.gas_price_estimated def gas_used(self) -> Optional[int]: """ :return: Gas used by the transaction if it was executed """ if self.tx_hash: try: return EthereumTx.objects.get(tx_hash=self.tx_hash).gas_used except EthereumTx.DoesNotExist: return None
class ModuleTransaction(TimeStampedModel): internal_tx = models.OneToOneField(InternalTx, on_delete=models.CASCADE, related_name='module_tx', primary_key=True) safe = EthereumAddressField( db_index=True ) # Just for convenience, it could be retrieved from `internal_tx` module = EthereumAddressField( db_index=True ) # Just for convenience, it could be retrieved from `internal_tx` to = EthereumAddressField(db_index=True) value = Uint256Field() data = models.BinaryField(null=True) operation = models.PositiveSmallIntegerField( choices=[(tag.value, tag.name) for tag in SafeOperation]) failed = models.BooleanField(default=False) def __str__(self): if self.value: return f'{self.safe} - {self.to} - {self.value}' else: return f'{self.safe} - {self.to} - {HexBytes(self.data.tobytes()).hex()[:8]}' @property def execution_date(self) -> Optional[datetime.datetime]: if self.internal_tx.ethereum_tx_id and self.internal_tx.ethereum_tx.block_id is not None: return self.internal_tx.ethereum_tx.block.timestamp return None
class EthereumTx(TimeStampedModel): objects = EthereumTxManager() block = models.ForeignKey(EthereumBlock, on_delete=models.CASCADE, null=True, default=None, related_name='txs') # If mined tx_hash = Sha3HashField(unique=True, primary_key=True) gas_used = Uint256Field(null=True, default=None) # If mined _from = EthereumAddressField(null=True, db_index=True) gas = Uint256Field() gas_price = Uint256Field() data = models.BinaryField(null=True) nonce = Uint256Field() to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() def __str__(self): return '{} from={} to={}'.format(self.tx_hash, self._from, self.to)
class EthereumTx(TimeStampedModel): objects = EthereumTxManager() block = models.ForeignKey( EthereumBlock, on_delete=models.CASCADE, null=True, blank=True, default=None, related_name="txs", ) # If mined tx_hash = Sha3HashField(unique=True, primary_key=True) gas_used = Uint256Field(null=True, blank=True, default=None) # If mined status = models.IntegerField( null=True, blank=True, default=None, db_index=True) # If mined. Old txs don't have `status` transaction_index = models.PositiveIntegerField(null=True, blank=True, default=None) # If mined _from = EthereumAddressField(null=True, db_index=True) gas = Uint256Field() gas_price = Uint256Field() data = models.BinaryField(null=True, blank=True) nonce = Uint256Field() to = EthereumAddressField(null=True, blank=True, db_index=True) value = Uint256Field() max_fee_per_gas = Uint256Field(default=0) max_priority_fee_per_gas = Uint256Field(default=0) def __str__(self): return "{} status={} from={} to={}".format(self.tx_hash, self.status, self._from, self.to) @property def success(self) -> Optional[bool]: if self.status is not None: return self.status == 1 @property def fee(self) -> int: return self.gas * self.gas_price def is_eip1559(self): return self.max_fee_per_gas or self.max_priority_fee_per_gas def as_tx_dict(self) -> TxParams: tx_params: TxParams = { "data": bytes(self.data) if self.data else b"", "from": self._from, "gas": self.gas, "gasPrice": self.gas_price, "nonce": self.nonce, "to": self.to, "value": self.value, } if self.is_eip1559(): tx_params["maxFeePerGas"] = self.max_fee_per_gas tx_params["maxPriorityFeePerGas"] = self.max_priority_fee_per_gas else: tx_params["gasPrice"] = self.gas_price return tx_params
class SafeCreation(TimeStampedModel): objects = SafeCreationManager() deployer = EthereumAddressField(primary_key=True) safe = models.OneToOneField(SafeContract, on_delete=models.CASCADE) master_copy = EthereumAddressField() funder = EthereumAddressField(null=True) owners = ArrayField(EthereumAddressField()) threshold = Uint256Field() payment = Uint256Field() tx_hash = Sha3HashField(unique=True) gas = Uint256Field() gas_price = Uint256Field() payment_token = EthereumAddressField(null=True) value = Uint256Field() v = models.PositiveSmallIntegerField() r = Uint256Field() s = Uint256Field() data = models.BinaryField(null=True) signed_tx = models.BinaryField(null=True) def __str__(self): return 'Safe {} - Deployer {}'.format(self.safe, self.deployer) def wei_deploy_cost(self) -> int: """ :return: int: Cost to deploy the contract in wei """ return self.gas * self.gas_price
def ether_txs(self): return self.filter( call_type=EthereumTxCallType.CALL.value, value__gt=0).annotate( transaction_hash=F('ethereum_tx_id'), block_number=F('ethereum_tx__block_id'), execution_date=F('ethereum_tx__block__timestamp'), token_id=Value(None, output_field=Uint256Field()), token_address=Value(None, output_field=EthereumAddressField()), ).order_by('-ethereum_tx__block_id')
class SafeStatus(models.Model): objects = SafeStatusManager.from_queryset(SafeStatusQuerySet)() internal_tx = models.OneToOneField(InternalTx, on_delete=models.CASCADE, related_name='safe_status', primary_key=True) address = EthereumAddressField(db_index=True) owners = ArrayField(EthereumAddressField()) threshold = Uint256Field() nonce = Uint256Field(default=0) master_copy = EthereumAddressField() fallback_handler = EthereumAddressField() guard = EthereumAddressField(default=None, null=True) enabled_modules = ArrayField(EthereumAddressField(), default=list) class Meta: indexes = [ Index(fields=['address', '-nonce' ]), # Index on address and nonce DESC GinIndex(fields=['owners']) ] unique_together = (('internal_tx', 'address'), ) verbose_name_plural = 'Safe statuses' def __str__(self): return f'safe={self.address} threshold={self.threshold} owners={self.owners} nonce={self.nonce}' @property def block_number(self): return self.internal_tx.ethereum_tx.block_id def is_corrupted(self): """ SafeStatus nonce must be incremental. If current nonce is bigger than the number of SafeStatus for that Safe something is wrong. There could be more SafeStatus than nonce (e.g. a call to a MultiSend adding owners and enabling a Module in the same contract `execTransaction`) :return: True if corrupted, False otherwise """ return self.__class__.objects.filter( address=self.address, nonce__lte=self.nonce).count() <= self.nonce def store_new(self, internal_tx: InternalTx) -> None: self.internal_tx = internal_tx return self.save()
class MultisigTransaction(TimeStampedModel): objects = MultisigTransactionQuerySet.as_manager() safe_tx_hash = Sha3HashField(primary_key=True) safe = EthereumAddressField() ethereum_tx = models.ForeignKey(EthereumTx, null=True, default=None, blank=True, on_delete=models.SET_NULL, related_name='multisig_txs') to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() data = models.BinaryField(null=True) operation = models.PositiveSmallIntegerField( choices=[(tag.value, tag.name) for tag in SafeOperation]) safe_tx_gas = Uint256Field() base_gas = Uint256Field() gas_price = Uint256Field() gas_token = EthereumAddressField(null=True) refund_receiver = EthereumAddressField(null=True) signatures = models.BinaryField(null=True) # When tx is executed nonce = Uint256Field() def __str__(self): return f'{self.safe} - {self.nonce} - {self.safe_tx_hash}' @property def execution_date(self) -> Optional[datetime.datetime]: if self.ethereum_tx_id and self.ethereum_tx.block: return self.ethereum_tx.block.timestamp return None @property def executed(self) -> bool: return bool(self.ethereum_tx_id and (self.ethereum_tx.block_id is not None)) def owners(self) -> Optional[List[str]]: if not self.signatures: return None else: # TODO Get owners from signatures. Not very trivial return []
class ModuleTransaction(TimeStampedModel): internal_tx = models.OneToOneField(InternalTx, on_delete=models.CASCADE, related_name='module_tx', primary_key=True) safe = EthereumAddressField( db_index=True ) # Just for convenience, it could be retrieved from `internal_tx` module = EthereumAddressField( db_index=True ) # Just for convenience, it could be retrieved from `internal_tx` to = EthereumAddressField(db_index=True) value = Uint256Field() data = models.BinaryField(null=True) operation = models.PositiveSmallIntegerField( choices=[(tag.value, tag.name) for tag in SafeOperation]) def __str__(self): if self.value: return f'{self.safe} - {self.to} - {self.value}' else: return f'{self.safe} - {self.to} - {HexBytes(self.data.tobytes()).hex()[:8]}'
def get_all_tx_hashes( self, safe_address: str, executed: bool = False, queued: bool = True, trusted: bool = True, ) -> QuerySet: """ Build a queryset with hashes for every tx for a Safe for paginated filtering. In the case of Multisig Transactions, as some of them are not mined, we use the SafeTxHash Criteria for building this list: - Return only multisig txs with `nonce < current Safe Nonce` - The endpoint should only show incoming transactions that have been mined - The transactions should be sorted by execution date. If an outgoing transaction doesn't have an execution date the execution date of the transaction with the same nonce that has been executed should be taken. - Incoming and outgoing transfers or Eth/tokens must be under a multisig/module tx if triggered by one. Otherwise they should have their own entry in the list using a EthereumTx :param safe_address: :param executed: By default `False`, all transactions are returned. With `True`, just txs executed are returned. :param queued: By default `True`, all transactions are returned. With `False`, just txs with `nonce < current Safe Nonce` are returned. :param trusted: By default `True`, just txs that are trusted are returned (with at least one confirmation, sent by a delegate or indexed). With `False` all txs are returned :return: List with tx hashes sorted by date (newest first) """ # If tx is not mined, get the execution date of a tx mined with the same nonce case = Case( When( ethereum_tx__block=None, then=MultisigTransaction.objects.filter( safe=OuterRef("safe"), nonce=OuterRef("nonce")).exclude( ethereum_tx__block=None).values( "ethereum_tx__block__timestamp"), ), default=F("ethereum_tx__block__timestamp"), ) multisig_safe_tx_ids = ( MultisigTransaction.objects.filter(safe=safe_address).annotate( execution_date=case, block=F("ethereum_tx__block_id"), safe_nonce=F("nonce"), ).values( "safe_tx_hash", # Tricky, we will merge SafeTx hashes with EthereumTx hashes "execution_date", "created", "block", "safe_nonce", )) # Block is needed to get stable ordering if not queued: # Filter out txs with nonce >= Safe nonce last_nonce_query = (MultisigTransaction.objects.filter( safe=safe_address).exclude( ethereum_tx=None).order_by("-nonce").values("nonce")) multisig_safe_tx_ids = multisig_safe_tx_ids.filter( nonce__lte=Subquery(last_nonce_query[:1])) if trusted: # Just show trusted transactions multisig_safe_tx_ids = multisig_safe_tx_ids.filter(trusted=True) if executed: multisig_safe_tx_ids = multisig_safe_tx_ids.exclude( ethereum_tx__block=None) # Get module txs module_tx_ids = (ModuleTransaction.objects.filter( safe=safe_address).annotate( execution_date=F("internal_tx__ethereum_tx__block__timestamp"), block=F("internal_tx__ethereum_tx__block_id"), safe_nonce=Value(0, output_field=Uint256Field()), ).values( "internal_tx__ethereum_tx_id", "execution_date", "created", "block", "safe_nonce", ).distinct()) multisig_hashes = (MultisigTransaction.objects.filter( safe=safe_address).exclude( ethereum_tx=None).values("ethereum_tx_id")) module_hashes = ModuleTransaction.objects.filter( safe=safe_address).values("internal_tx__ethereum_tx_id") multisig_and_module_hashes = multisig_hashes.union(module_hashes) # Get incoming/outgoing tokens not included on Multisig or Module txs. # Outgoing tokens can be triggered by another user after the Safe calls `approve`, that's why it will not # always appear as a MultisigTransaction erc20_tx_ids = (ERC20Transfer.objects.to_or_from(safe_address).exclude( ethereum_tx__in=multisig_and_module_hashes).annotate( execution_date=F("timestamp"), created=F("timestamp"), block=F("block_number"), safe_nonce=Value(0, output_field=Uint256Field()), ).values("ethereum_tx_id", "execution_date", "created", "block", "safe_nonce").distinct()) erc721_tx_ids = ( ERC721Transfer.objects.to_or_from(safe_address).exclude( ethereum_tx__in=multisig_and_module_hashes).annotate( execution_date=F("timestamp"), created=F("timestamp"), block=F("block_number"), safe_nonce=Value(0, output_field=Uint256Field()), ).values("ethereum_tx_id", "execution_date", "created", "block", "safe_nonce").distinct()) # Get incoming txs not included on Multisig or Module txs internal_tx_ids = (InternalTx.objects.filter( call_type=EthereumTxCallType.CALL.value, value__gt=0, to=safe_address, ).exclude(ethereum_tx__in=multisig_and_module_hashes).annotate( execution_date=F("timestamp"), created=F("timestamp"), block=F("block_number"), safe_nonce=Value(0, output_field=Uint256Field()), ).values("ethereum_tx_id", "execution_date", "created", "block", "safe_nonce").distinct()) # Tricky, we merge SafeTx hashes with EthereumTx hashes queryset = ( multisig_safe_tx_ids.union(erc20_tx_ids).union(erc721_tx_ids). union(internal_tx_ids).union(module_tx_ids).order_by( "-execution_date", "-safe_nonce", "block", "-created")) # Order by block because `block_number < NULL`, so txs mined will have preference, # and `created` to get always the same ordering with not executed transactions, as they will share # the same `execution_date` that the mined tx return queryset
class InternalTx(models.Model): objects = InternalTxManager.from_queryset(InternalTxQuerySet)() ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='internal_txs') _from = EthereumAddressField( null=True, db_index=True) # For SELF-DESTRUCT it can be null gas = Uint256Field() data = models.BinaryField(null=True) # `input` for Call, `init` for Create to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() gas_used = Uint256Field() contract_address = EthereumAddressField(null=True, db_index=True) # Create code = models.BinaryField(null=True) # Create output = models.BinaryField(null=True) # Call refund_address = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT tx_type = models.PositiveSmallIntegerField(choices=[ (tag.value, tag.name) for tag in EthereumTxType ], db_index=True) call_type = models.PositiveSmallIntegerField( null=True, choices=[(tag.value, tag.name) for tag in EthereumTxCallType], db_index=True) # Call trace_address = models.CharField( max_length=600) # Stringified traceAddress error = models.CharField(max_length=200, null=True) class Meta: unique_together = (('ethereum_tx', 'trace_address'), ) def __str__(self): if self.to: return 'Internal tx hash={} from={} to={}'.format( self.ethereum_tx_id, self._from, self.to) else: return 'Internal tx hash={} from={}'.format( self.ethereum_tx_id, self._from) @property def block_number(self): return self.ethereum_tx.block_id @property def created(self): return self.ethereum_tx.block.timestamp @property def can_be_decoded(self) -> bool: return bool(self.is_delegate_call and not self.error and self.data and self.ethereum_tx.success) @property def is_call(self): return EthereumTxType(self.tx_type) == EthereumTxType.CALL @property def is_create(self): return EthereumTxType(self.tx_type) == EthereumTxType.CREATE @property def is_decoded(self): try: return bool(self.decoded_tx) except InternalTxDecoded.DoesNotExist: return False @property def is_delegate_call(self) -> bool: if self.call_type is None: return False else: return EthereumTxCallType( self.call_type) == EthereumTxCallType.DELEGATE_CALL @property def is_ether_transfer(self) -> bool: return self.call_type == EthereumTxCallType.CALL.value and self.value > 0 @property def is_relevant(self): return self.can_be_decoded or self.is_ether_transfer or self.contract_address @property def trace_address_as_list(self) -> List[int]: if not self.trace_address: return [] else: return [int(x) for x in self.trace_address.split(',')]
class InternalTx(models.Model): objects = InternalTxManager.from_queryset(InternalTxQuerySet)() ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='internal_txs') _from = EthereumAddressField( null=True, db_index=True) # For SELF-DESTRUCT it can be null gas = Uint256Field() data = models.BinaryField(null=True) # `input` for Call, `init` for Create to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() gas_used = Uint256Field() contract_address = EthereumAddressField(null=True, db_index=True) # Create code = models.BinaryField(null=True) # Create output = models.BinaryField(null=True) # Call refund_address = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT tx_type = models.PositiveSmallIntegerField(choices=[ (tag.value, tag.name) for tag in EthereumTxType ], db_index=True) call_type = models.PositiveSmallIntegerField( null=True, choices=[(tag.value, tag.name) for tag in EthereumTxCallType], db_index=True) # Call trace_address = models.CharField( max_length=600) # Stringified traceAddress error = models.CharField(max_length=200, null=True) class Meta: unique_together = (('ethereum_tx', 'trace_address'), ) def __str__(self): if self.to: return 'Internal tx hash={} from={} to={}'.format( self.ethereum_tx_id, self._from, self.to) else: return 'Internal tx hash={} from={}'.format( self.ethereum_tx_id, self._from) @property def block_number(self): return self.ethereum_tx.block_id @property def can_be_decoded(self): return (self.is_delegate_call and not self.error and self.data and self.ethereum_tx.success) @property def is_call(self): return EthereumTxType(self.tx_type) == EthereumTxType.CALL @property def is_decoded(self): try: return bool(self.decoded_tx) except InternalTxDecoded.DoesNotExist: return False @property def is_delegate_call(self) -> bool: if self.call_type is None: return False else: return EthereumTxCallType( self.call_type) == EthereumTxCallType.DELEGATE_CALL def get_next_trace(self) -> Optional['InternalTx']: internal_txs = InternalTx.objects.filter( ethereum_tx=self.ethereum_tx).order_by('trace_address') traces = [it.trace_address for it in internal_txs] index = traces.index(self.trace_address) try: return internal_txs[index + 1] except IndexError: return None def get_previous_trace(self) -> Optional['InternalTx']: internal_txs = InternalTx.objects.filter( ethereum_tx=self.ethereum_tx).order_by('trace_address') traces = [it.trace_address for it in internal_txs] index = traces.index(self.trace_address) try: return internal_txs[index - 1] except IndexError: return None
class InternalTx(models.Model): objects = InternalTxManager.from_queryset(InternalTxQuerySet)() ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='internal_txs') _from = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT it can be null gas = Uint256Field() data = models.BinaryField(null=True) # `input` for Call, `init` for Create to = EthereumAddressField(null=True, db_index=True) value = Uint256Field() gas_used = Uint256Field() contract_address = EthereumAddressField(null=True, db_index=True) # Create code = models.BinaryField(null=True) # Create output = models.BinaryField(null=True) # Call refund_address = EthereumAddressField(null=True, db_index=True) # For SELF-DESTRUCT tx_type = models.PositiveSmallIntegerField(choices=[(tag.value, tag.name) for tag in EthereumTxType], db_index=True) call_type = models.PositiveSmallIntegerField(null=True, choices=[(tag.value, tag.name) for tag in EthereumTxCallType], db_index=True) # Call trace_address = models.CharField(max_length=600) # Stringified traceAddress error = models.CharField(max_length=200, null=True) class Meta: unique_together = (('ethereum_tx', 'trace_address'),) def __str__(self): if self.to: return 'Internal tx hash={} from={} to={}'.format(self.ethereum_tx_id, self._from, self.to) else: return 'Internal tx hash={} from={}'.format(self.ethereum_tx_id, self._from) @property def block_number(self): return self.ethereum_tx.block_id @property def can_be_decoded(self) -> bool: return bool(self.is_delegate_call and not self.error and self.data and self.ethereum_tx.success and not self.parent_is_errored()) @property def is_call(self): return EthereumTxType(self.tx_type) == EthereumTxType.CALL @property def is_create(self): return EthereumTxType(self.tx_type) == EthereumTxType.CREATE @property def is_decoded(self): try: return bool(self.decoded_tx) except InternalTxDecoded.DoesNotExist: return False @property def is_delegate_call(self) -> bool: if self.call_type is None: return False else: return EthereumTxCallType(self.call_type) == EthereumTxCallType.DELEGATE_CALL @property def is_ether_transfer(self) -> bool: return self.call_type == EthereumTxCallType.CALL.value and self.value > 0 def parent_is_errored(self) -> bool: """ Check if parent trace has been errored (`trace_address` is contained in children `trace_address`) :return: """ count = self.__class__.objects.annotate( child_trace_address=Value(self.trace_address, output_field=models.CharField()) ).filter( ethereum_tx=self.ethereum_tx, child_trace_address__startswith=F('trace_address') ).exclude( error=None ).count() return bool(count) def get_next_trace(self) -> Optional['InternalTx']: internal_txs = InternalTx.objects.filter(ethereum_tx=self.ethereum_tx).order_by('trace_address') traces = [it.trace_address for it in internal_txs] index = traces.index(self.trace_address) try: return internal_txs[index + 1] except IndexError: return None def get_previous_trace(self, no_delegate_calls=False) -> Optional['InternalTx']: """ :param no_delegate_calls: If True filter out delegate calls :return: """ internal_txs = InternalTx.objects.filter(ethereum_tx=self.ethereum_tx).order_by('trace_address') traces = [it.trace_address for it in internal_txs] index = traces.index(self.trace_address) if (index - 1) >= 0: return internal_txs[index - 1] else: return None def get_previous_module_trace(self) -> Optional['InternalTx']: """ Current trace should be `delegate call` to the proxy contract. We skip previous one and search for the previous call trace :return: """ internal_txs = InternalTx.objects.filter(ethereum_tx=self.ethereum_tx).order_by('trace_address') traces = [it.trace_address for it in internal_txs] index = traces.index(self.trace_address) - 2 while index >= 0: internal_tx = internal_txs[index] if not internal_tx.is_delegate_call: return internal_tx else: index -= 1 return None