コード例 #1
0
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()
コード例 #2
0
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)
コード例 #3
0
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
コード例 #4
0
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)
コード例 #5
0
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
コード例 #6
0
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]
コード例 #7
0
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'
            ])
コード例 #8
0
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
コード例 #9
0
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
コード例 #10
0
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)
コード例 #11
0
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
コード例 #12
0
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
コード例 #13
0
 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')
コード例 #14
0
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()
コード例 #15
0
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 []
コード例 #16
0
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]}'
コード例 #17
0
    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
コード例 #18
0
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(',')]
コード例 #19
0
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
コード例 #20
0
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