import json
import os
import dateutil.parser
from pytezos import ContractInterface
from django.db import models
from django.db.models import signals
from django.dispatch import receiver
from djwebdapp.models import Transaction
from djwebdapp.normalizers import Normalizer
[docs]class TezosTransaction(Transaction):
"""
Base class for tezos transactions.
.. py:attribute:: micheline
Smart contract micheline JSON code.
"""
unit_smallest = 'xTZ'
contract = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='call_set',
null=True,
blank=True,
help_text='Smart contract, appliable to method call',
)
micheline = models.JSONField(
default=dict,
blank=True,
null=True,
help_text='Smart contract Micheline, if this is a smart contract',
)
caller = models.ForeignKey(
"TezosTransaction",
on_delete=models.CASCADE,
null=True,
related_name="_internal_calls",
)
[docs] def save(self, *args, **kwargs):
"""
Set :py:attr:`~djwebdapp.models.Transaction.has_code` if
:py:attr:`~micheline`.
"""
if self.micheline:
self.has_code = True
return super().save(*args, **kwargs)
@property
def interface(self):
return ContractInterface.from_micheline(self.micheline)
@property
def is_internal(self):
return bool(self.caller_id)
@property
def internal_calls(self):
if self.is_internal:
txgroup_internal_calls_qs = self.caller._internal_calls
else:
txgroup_internal_calls_qs = self._internal_calls
tx_internal_calls_qs = txgroup_internal_calls_qs.filter(
nonce__gte=self.nonce if self.nonce else 0,
sender__address=self.contract.address,
)
return tx_internal_calls_qs.order_by("nonce").all()
@property
def storage(self):
from pytezos.operation.result import OperationResult
if self.metadata:
tx_op = OperationResult.from_transaction(self.metadata)
contract = self.contract_subclass()
if contract:
return contract.interface.storage.decode(tx_op.storage)
@property
def timestamp(self):
client = self.provider.client
timestamp_str = client.shell.blocks[self.level].header.shell()
return dateutil.parser.isoparse(timestamp_str["timestamp"])
@receiver(signals.pre_save, sender=TezosTransaction)
def contract_micheline(sender, instance, **kwargs):
if not instance.address or instance.micheline:
return
if instance.kind != 'contract':
return # only fetch micheline for contracts
interface = instance.blockchain.provider.client.contract(instance.address)
instance.micheline = interface.to_micheline()
[docs]class TezosContract(TezosTransaction):
contract_name = None
normalizer_class = Normalizer
class Meta:
proxy = True
[docs] def save(self, *args, **kwargs):
"""
Set :py:attr:`~djwebdapp_tezos.models.TezosTransaction.micheline` if
:py:attr:`~djwebdapp.models.Transaction.contract_name` is set.
"""
if self.contract_name and not self.micheline:
self.micheline = self.get_contract_interface().to_micheline()
return super().save(*args, **kwargs)
def get_contract_interface(self):
if self.micheline:
return ContractInterface.from_micheline(self.micheline)
elif os.path.exists(f'{self.contract_path}.json'):
with open(f'{self.contract_path}.json') as micheline:
return ContractInterface.from_micheline(
json.loads(micheline.read())
)
elif os.path.exists(f'{self.contract_path}.tz'):
with open(f'{self.contract_path}.tz') as michelson:
return ContractInterface.from_michelson(michelson.read())
raise Exception('Contract sources could not be found')
def get_init_storage(self):
raise NotImplementedError
def get_michelson_storage(self):
contract_interface = self.get_contract_interface()
return contract_interface.storage.encode(self.get_init_storage())
[docs] def get_args(self):
return self.get_michelson_storage()
[docs]class TezosCall(TezosTransaction):
entrypoint = None
normalizer_class = None
target_contract = None
class Meta:
proxy = True