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 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
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 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 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 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 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 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 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)
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 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}'
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}'
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 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
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)
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)
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}'
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)
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')
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')
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}'
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 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()
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
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}'
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]}'
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}'
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}'