import logging
from hexbytes import HexBytes
from web3 import Web3
from django.conf import settings
from djwebdapp.models import Account
from djwebdapp_ethereum.models import EthereumTransaction
from djwebdapp.provider import Provider
[docs]class EthereumProvider(Provider):
logger = logging.getLogger('djwebdapp_ethereum')
transaction_class = EthereumTransaction
[docs] def generate_secret_key(self):
wallet = self.client.eth.account.create()
return wallet.address, bytes(wallet.key)
[docs] def get_client(self, **kwargs):
endpoint = self.blockchain.node_set.first().endpoint
client = Web3(Web3.HTTPProvider(endpoint))
if settings.DEBUG and not self.wallet: # geth default account
client.eth.default_account = client.eth.accounts[0]
if 'ethlocal' in endpoint or 'localhost' in endpoint:
from web3.middleware import geth_poa_middleware
client.middleware_onion.inject(geth_poa_middleware, layer=0)
return client
[docs] def get_balance(self, address=None):
weis = self.client.eth.get_balance(
address
or getattr(self.wallet, 'address', False)
or self.client.eth.default_account
)
return self.client.from_wei(weis, 'ether')
@property
def head(self):
"""
Return the current block number.
"""
return self.client.eth.get_block_number()
[docs] def index_level(self, level):
block = self.client.eth.get_block(level)
for hash in block.transactions:
transaction = self.client.eth.get_transaction(hash)
to = transaction.get('to', None)
if to is None and transaction['hash'].hex() in self.hashes:
self.index_contract(level, transaction)
elif to in self.addresses:
self.index_call(level, transaction)
def index_contract(self, level, transaction):
self.logger.info(f'Syncing origination {transaction["hash"]}')
contract = EthereumTransaction.objects.get(
blockchain=self.blockchain,
hash=transaction['hash'].hex(),
)
contract.level = level
contract.gas = transaction['gas']
contract.metadata = self.json(transaction)
contract.state_set('done')
def index_call(self, level, transaction):
self.logger.info(f'EthereumProvider.index_call({transaction})')
contract = EthereumTransaction.objects.get(
blockchain=self.blockchain,
address=transaction['to'],
)
call = contract.call_set.select_subclasses().filter(
hash=transaction['hash'].hex(),
).first()
if not call:
call = EthereumTransaction(
hash=transaction['hash'].hex(),
contract=contract,
blockchain=self.blockchain,
)
call.metadata = self.json(transaction)
call.gas = transaction['gas']
call.level = level
call.sender, _ = Account.objects.get_or_create(
address=transaction['from'],
blockchain=self.blockchain,
)
interface = self.client.eth.contract(
address=contract.address,
abi=contract.abi,
)
fn, args = interface.decode_function_input(call.metadata['input'])
call.function = fn.fn_name
call.args = args
call.state_set('done')
def json(self, transaction):
return {
key: str(value) if isinstance(value, HexBytes) else value
for key, value in transaction.items()
}
def send(self, transaction):
Contract = self.client.eth.contract( # noqa
abi=transaction.contract.abi,
address=transaction.contract.address,
)
funcs = Contract.find_functions_by_name(transaction.function)
if not funcs:
raise Exception(
f'{transaction.function} not found in {transaction.contract}'
)
func = funcs[0]
args = transaction.get_args()
for i, inp in enumerate(func.abi.get('inputs', [])):
if inp['type'].startswith('bytes32'):
args[i] = self.client.toBytes(hexstr=args[i])
tx = func(*args)
transaction.hash = self.write_transaction(transaction.sender, tx)
[docs] def deploy(self, transaction):
self.logger.debug(f'{transaction}.deploy(): start')
transaction.level = self.head
if transaction.kind == 'contract':
transaction.sender.last_level = self.head
self.originate(transaction)
transaction.sender.save()
elif transaction.kind == 'function':
transaction.sender.last_level = self.head
self.send(transaction)
transaction.sender.save()
elif transaction.kind == 'transfer':
transaction.hash = self.transfer(transaction)
else:
transaction.error = f'Unknown transaction kind {transaction.kind}'
transaction.state_set('failed')
return
self.logger.info(f'{transaction}.deploy(): success')
def transfer(self, transaction):
tx = {
'to': transaction.receiver.address,
'from': transaction.sender.address,
'value': self.client.to_wei(transaction.amount, 'ether'),
}
tx['gas'] = self.client.eth.estimate_gas(tx)
tx['gasPrice'] = self.client.eth.gas_price
tx['nonce'] = self.client.eth.get_transaction_count(
transaction.sender.address
)
tx['chainId'] = self.client.eth.chain_id
signed_txn = self.client.eth.account.sign_transaction(
tx,
private_key=transaction.sender.get_secret_key(),
)
self.client.eth.send_raw_transaction(signed_txn.rawTransaction)
return self.client.to_hex(
self.client.keccak(signed_txn.rawTransaction)
)
def originate(self, transaction):
Contract = self.client.eth.contract( # noqa
abi=transaction.abi,
bytecode=transaction.bytecode,
)
tx = Contract.constructor(*transaction.get_args())
transaction.hash = self.write_transaction(transaction.sender, tx)
receipt = self.client.eth.wait_for_transaction_receipt(
transaction.hash)
transaction.address = receipt.contractAddress
def write_transaction(self, sender, tx):
nonce = self.client.eth.get_transaction_count(sender.address)
options = {
'from': sender.address,
'nonce': nonce,
}
options['gas'] = self.client.eth.estimate_gas(
tx.build_transaction(options)
)
built = tx.build_transaction(options)
signed_txn = self.client.eth.account.sign_transaction(
built,
private_key=sender.get_secret_key(),
)
self.client.eth.send_raw_transaction(signed_txn.rawTransaction)
return self.client.to_hex(
self.client.keccak(signed_txn.rawTransaction)
)