コード例 #1
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)
コード例 #2
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
コード例 #3
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()
コード例 #4
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
コード例 #5
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)
コード例 #6
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
コード例 #7
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
コード例 #8
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
コード例 #9
0
class SafeContract(TimeStampedModel):
    objects = SafeContractManager.from_queryset(SafeContractQuerySet)()
    address = EthereumAddressField(primary_key=True)
    master_copy = EthereumAddressField()

    def __str__(self):
        return 'Safe=%s Master-copy=%s' % (self.address, self.master_copy)

    def get_tokens_with_balance(self) -> List[Dict[str, Any]]:
        return EthereumEvent.objects.erc20_tokens_with_balance(self.address)
コード例 #10
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]
コード例 #11
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'
            ])
コード例 #12
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
コード例 #13
0
class MultisigConfirmation(TimeStampedModel):
    objects = MultisigConfirmationQuerySet.as_manager()
    ethereum_tx = models.ForeignKey(
        EthereumTx,
        on_delete=models.CASCADE,
        related_name='multisig_confirmations',
        null=True)  # `null=True` for signature confirmations
    multisig_transaction = models.ForeignKey(MultisigTransaction,
                                             on_delete=models.CASCADE,
                                             null=True,
                                             related_name="confirmations")
    multisig_transaction_hash = Sha3HashField(
        null=True,
        db_index=True)  # Use this while we don't have a `multisig_transaction`
    owner = EthereumAddressField()

    signature = HexField(null=True, default=None,
                         max_length=500)  # Off chain signatures

    class Meta:
        unique_together = (('multisig_transaction_hash', 'owner'), )

    def __str__(self):
        if self.multisig_transaction_id:
            return f'Confirmation of owner={self.owner} for transaction-hash={self.multisig_transaction_hash}'
        else:
            return f'Confirmation of owner={self.owner} for existing transaction={self.multisig_transaction_hash}'
コード例 #14
0
class MultisigConfirmation(TimeStampedModel):
    objects = MultisigConfirmationManager.from_queryset(
        MultisigConfirmationQuerySet)()
    ethereum_tx = models.ForeignKey(
        EthereumTx,
        on_delete=models.CASCADE,
        related_name='multisig_confirmations',
        null=True)  # `null=True` for signature confirmations
    multisig_transaction = models.ForeignKey(MultisigTransaction,
                                             on_delete=models.CASCADE,
                                             null=True,
                                             related_name='confirmations')
    multisig_transaction_hash = Sha3HashField(
        null=True,
        db_index=True)  # Use this while we don't have a `multisig_transaction`
    owner = EthereumAddressField()

    signature = HexField(null=True, default=None, max_length=2000)
    signature_type = models.PositiveSmallIntegerField(choices=[
        (tag.value, tag.name) for tag in SafeSignatureType
    ],
                                                      db_index=True)

    class Meta:
        unique_together = (('multisig_transaction_hash', 'owner'), )
        ordering = ['created']

    def __str__(self):
        if self.multisig_transaction_id:
            return f'Confirmation of owner={self.owner} for transaction-hash={self.multisig_transaction_hash}'
        else:
            return f'Confirmation of owner={self.owner} for existing transaction={self.multisig_transaction_hash}'
コード例 #15
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)
コード例 #16
0
class EthereumEvent(models.Model):
    objects = EthereumEventManager.from_queryset(EthereumEventQuerySet)()
    ethereum_tx = models.ForeignKey(EthereumTx,
                                    on_delete=models.CASCADE,
                                    related_name='events')
    log_index = models.PositiveIntegerField()
    address = EthereumAddressField(db_index=True)
    topic = Sha3HashField(db_index=True)
    topics = ArrayField(Sha3HashField())
    arguments = JSONField()

    class Meta:
        indexes = [GinIndex(fields=['arguments'])]
        unique_together = (('ethereum_tx', 'log_index'), )
        # There are also 2 indexes created manually by 0026 migration, both Btree for arguments->to and arguments->from
        # To use that indexes json queries must be rewritten to use `::text` fields

    def __str__(self):
        return f'Tx-hash={self.ethereum_tx_id} Log-index={self.log_index} Topic={self.topic} Arguments={self.arguments}'

    @property
    def created(self):
        return self.ethereum_tx.block.timestamp

    def is_erc20(self) -> bool:
        return self.topic == ERC20_721_TRANSFER_TOPIC and 'value' in self.arguments and 'to' in self.arguments

    def is_erc721(self) -> bool:
        return self.topic == ERC20_721_TRANSFER_TOPIC and 'tokenId' in self.arguments and 'to' in self.arguments
コード例 #17
0
class Token(models.Model):
    objects = TokenQuerySet.as_manager()
    address = EthereumAddressField(primary_key=True)
    name = models.CharField(max_length=60)
    symbol = models.CharField(max_length=60)
    decimals = models.PositiveSmallIntegerField(
        db_index=True)  # For ERC721 tokens decimals=0
    logo_uri = models.CharField(blank=True, max_length=300, default='')
    trusted = models.BooleanField(default=False)

    def __str__(self):
        if self.decimals:
            return f'ERC20 - {self.name} - {self.address}'
        else:
            return f'ERC721 - {self.name} - {self.address}'

    def set_trusted(self) -> None:
        self.trusted = True
        return self.save(update_fields=['trusted'])

    def get_full_logo_uri(self):
        if urlparse(self.logo_uri).netloc:
            # Absolute uri stored
            return self.logo_uri
        elif self.logo_uri:
            # Just path/filename with extension stored
            return urljoin(settings.TOKEN_LOGO_BASE_URI, self.logo_uri)
        else:
            # Generate logo uri based on configuration
            return urljoin(settings.TOKEN_LOGO_BASE_URI,
                           self.address + settings.TOKEN_LOGO_EXTENSION)
コード例 #18
0
class Token(models.Model):
    objects = TokenQuerySet.as_manager()
    address = EthereumAddressField(primary_key=True)
    name = models.CharField(max_length=60)
    symbol = models.CharField(max_length=60)
    description = models.TextField(blank=True)
    decimals = models.PositiveSmallIntegerField()
    logo_uri = models.CharField(blank=True, max_length=300)
    website_uri = models.URLField(blank=True)
    gas = models.BooleanField(default=False)
    price_oracles = models.ManyToManyField(PriceOracle, through=PriceOracleTicker)
    fixed_eth_conversion = models.DecimalField(null=True, default=None, blank=True, max_digits=25, decimal_places=15)
    relevance = models.PositiveIntegerField(default=100)

    def __str__(self):
        return '%s - %s' % (self.name, self.address)

    def get_eth_value(self) -> float:
        multiplier = 1e18 / 10 ** self.decimals
        if self.fixed_eth_conversion:  # `None` or `0` are ignored
            # Ether has 18 decimals, but maybe the token has a different number
            return round(multiplier * float(self.fixed_eth_conversion), 10)
        else:
            prices = [price_oracle_ticker.price for price_oracle_ticker in self.price_oracle_tickers.all()]
            prices = [price for price in prices if price is not None and price > 0]
            if prices:
                # Get the average price of the price oracles
                return multiplier * (sum(prices) / len(prices))
            else:
                raise CannotGetTokenPriceFromApi('There is no working provider for token=%s' % self.address)

    def calculate_payment(self, eth_payment: int) -> int:
        """
        Converts an ether payment to a token payment
        :param eth_payment: Ether payment (in wei)
        :return: Token payment equivalent for the ether value
        """
        return math.ceil(eth_payment / self.get_eth_value())

    def calculate_gas_price(self, gas_price: int, price_margin: float = 1.0) -> int:
        """
        Converts ether gas price to token's gas price
        :param gas_price: Regular ether gas price
        :param price_margin: Threshold to estimate a little higher, so tx will
        not be rejected in a few minutes
        :return:
        """
        return math.ceil(gas_price / self.get_eth_value() * price_margin)

    def get_full_logo_uri(self):
        if urlparse(self.logo_uri).netloc:
            # Absolute uri stored
            return self.logo_uri
        elif self.logo_uri:
            # Just path/filename with extension stored
            return urljoin(settings.TOKEN_LOGO_BASE_URI, self.logo_uri)
        else:
            # Generate logo uri based on configuration
            return urljoin(settings.TOKEN_LOGO_BASE_URI, self.address + settings.TOKEN_LOGO_EXTENSION)
コード例 #19
0
class SafeContractDelegate(models.Model):
    """
    The owners of the Safe can add users so they can propose/retrieve txs as if they were the owners of the Safe
    """
    objects = SafeContractDelegateManager()
    safe_contract = models.ForeignKey(SafeContract, on_delete=models.CASCADE, related_name='safe_contract_delegates')
    delegate = EthereumAddressField()
    delegator = EthereumAddressField()  # Owner who created the delegate
    label = models.CharField(max_length=50)
    read = models.BooleanField(default=True)  # For permissions in the future
    write = models.BooleanField(default=True)

    class Meta:
        unique_together = (('safe_contract', 'delegate'),)

    def __str__(self):
        return f'Delegate={self.delegate} for Safe={self.safe_contract_id} - Label={self.label}'
コード例 #20
0
class Token(models.Model):
    objects = TokenManager.from_queryset(TokenQuerySet)()
    address = EthereumAddressField(primary_key=True)
    name = models.CharField(max_length=60)
    symbol = models.CharField(max_length=60)
    decimals = models.PositiveSmallIntegerField(db_index=True,
                                                null=True, blank=True)  # For ERC721 tokens `decimals=None`
    logo_uri = models.CharField(blank=True, max_length=300, default='')
    events_bugged = models.BooleanField(default=False)  # If `True` token does not send `Transfer` event sometimes,
    # like `WETH` on minting
    spam = models.BooleanField(default=False)  # Spam and trusted cannot be both True
    trusted = models.BooleanField(default=False)

    class Meta:
        indexes = [
            models.Index(name='token_trusted_idx',
                         fields=['trusted'],
                         condition=Q(trusted=True)),
            models.Index(name='token_events_bugged_idx',
                         fields=['events_bugged'],
                         condition=Q(events_bugged=True)),
        ]

    def __str__(self):
        spam_text = 'SPAM ' if self.spam else ''
        if self.decimals:
            return f'{spam_text}ERC20 - {self.name} - {self.address}'
        else:
            return f'ERC721 - {self.name} - {self.address}'

    def clean(self):
        if self.trusted and self.spam:
            raise ValidationError('Spam and trusted cannot be both `True`')

    def is_erc20(self):
        return self.decimals is not None

    def is_erc721(self):
        return not self.is_erc20()

    def set_trusted(self) -> None:
        self.trusted = True
        return self.save(update_fields=['trusted'])

    def set_spam(self) -> None:
        self.spam = True
        return self.save(update_fields=['spam'])

    def get_full_logo_uri(self):
        if urlparse(self.logo_uri).netloc:
            # Absolute uri stored
            return self.logo_uri
        elif self.logo_uri:
            # Just path/filename with extension stored
            return urljoin(settings.TOKENS_LOGO_BASE_URI, self.logo_uri)
        else:
            # Generate logo uri based on configuration
            return urljoin(settings.TOKENS_LOGO_BASE_URI, self.address + settings.TOKENS_LOGO_EXTENSION)
コード例 #21
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')
コード例 #22
0
 def incoming_txs(self, address: str):
     return self.filter(
         to=address, call_type=EthereumTxCallType.CALL.value,
         value__gt=0).annotate(
             transaction_hash=F('ethereum_tx_id'),
             block_number=F('ethereum_tx__block_id'),
             token_address=Value(
                 None, output_field=EthereumAddressField())).order_by(
                     '-ethereum_tx__block_id')
コード例 #23
0
class Contract(models.Model):
    objects = ContractManager()
    address = EthereumAddressField(primary_key=True)
    name = models.CharField(max_length=200, blank=True, default='')
    contract_abi = models.ForeignKey(ContractAbi, on_delete=models.CASCADE, null=True, default=None,
                                     related_name='contracts')

    def __str__(self):
        has_abi = self.contract_abi_id is not None
        return f'Contract {self.address} - {self.name} - with abi {has_abi}'
コード例 #24
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 []
コード例 #25
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:
        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()
コード例 #26
0
class SafeContract(models.Model):
    objects = SafeContractManager.from_queryset(MonitoredAddressQuerySet)()
    address = EthereumAddressField(primary_key=True)
    ethereum_tx = models.ForeignKey(EthereumTx, on_delete=models.CASCADE, related_name='safe_contracts')
    erc20_block_number = models.IntegerField(default=0, db_index=True)  # Block number of last scan of erc20

    def __str__(self):
        return f'Safe address={self.address} - ethereum-tx={self.ethereum_tx_id}'

    @property
    def created_block_number(self) -> Optional[Type[int]]:
        if self.ethereum_tx:
            return self.ethereum_tx.block_id
コード例 #27
0
class FirebaseDeviceOwner(models.Model):
    firebase_device = models.ForeignKey(FirebaseDevice,
                                        on_delete=models.CASCADE,
                                        related_name='owners')
    owner = EthereumAddressField(db_index=True)

    class Meta:
        verbose_name = 'Firebase Device Owner'
        verbose_name_plural = 'Firebase Device Owners'
        unique_together = (('firebase_device', 'owner'), )

    def __str__(self):
        return f'{self.owner} for device {self.firebase_device_id}'
コード例 #28
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]}'
コード例 #29
0
class MonitoredAddress(models.Model):
    objects = MonitoredAddressManager.from_queryset(MonitoredAddressQuerySet)()
    address = EthereumAddressField(primary_key=True)
    initial_block_number = models.IntegerField(default=0)  # Block number when address received first tx
    tx_block_number = models.IntegerField(null=True, default=None,
                                          db_index=True)  # Block number when last internal tx scan ended

    class Meta:
        abstract = True
        verbose_name_plural = 'Monitored addresses'

    def __str__(self):
        return f'Address={self.address} - Initial-block-number={self.initial_block_number}' \
               f' - Tx-block-number={self.tx_block_number}'
コード例 #30
0
class WebHook(models.Model):
    objects = WebHookQuerySet.as_manager()
    address = EthereumAddressField(db_index=True, blank=True)
    url = models.URLField()
    # Configurable webhook types to listen to
    new_confirmation = models.BooleanField(default=True)
    pending_outgoing_transaction = models.BooleanField(default=True)
    new_executed_outgoing_transaction = models.BooleanField(default=True)
    new_incoming_transaction = models.BooleanField(default=True)

    class Meta:
        unique_together = (('address', 'url'), )

    def __str__(self):
        if self.address:
            return f'Webhook for safe={self.address} to url={self.url}'
        else:
            return f'Webhook to every address to url={self.url}'