-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: draft python library * doc: python client * Update docs/Deku-Canonical/deku_c_python.md Co-authored-by: Daniel Hines <[email protected]> * fix: lima support + readme update * fix: balance for empty data * fixup! fix: lima support + readme update Co-authored-by: Daniel Hines <[email protected]>
- Loading branch information
Showing
19 changed files
with
637 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
build/* | ||
dist/* | ||
env/* | ||
*.sw? | ||
*/*.sw? | ||
__pycache__ | ||
*/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Python Deku Client Library | ||
|
||
Please refer to the [online Deku | ||
documentation](https://deku.marigold.dev/docs/Deku-Canonical/deku_c_python) for installation and | ||
usage instructions. |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from typing import TypeAlias | ||
|
||
Address: TypeAlias = str | ||
|
||
|
||
def address_of_dto(yojson_repr): | ||
if yojson_repr[0] == "Originated": | ||
return yojson_repr[1]["contract"] | ||
elif yojson_repr[0] == "Implicit": | ||
return yojson_repr[1]["address"] | ||
else: | ||
raise ValueError(f"Ill-formed DTO: {yojson_repr}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
from attrs import define, field, validators | ||
from deku.core.address import Address | ||
from deku.core.ticket import TicketID | ||
from typing import Any | ||
|
||
|
||
@define | ||
class Transfer: | ||
sender: Address = field() | ||
receiver: Address = field() | ||
ticket_id: TicketID = field() | ||
amount: int = field(validator=validators.gt(0)) | ||
transaction_type = "TicketTransfer" | ||
|
||
def to_dto(self): | ||
return [ | ||
"Operation_ticket_transfer", | ||
{ | ||
"sender": self.sender, | ||
"receiver": self.receiver, | ||
"ticket_id": self.ticket_id.to_dto(), | ||
"amount": str(self.amount), | ||
}, | ||
] | ||
|
||
|
||
@define | ||
class Withdraw: | ||
sender: Address = field() | ||
owner: Address = field() | ||
ticket_id: TicketID = field() | ||
amount: int = field(validator=validators.gt(0)) | ||
transaction_type = "Withdraw" | ||
|
||
def address_to_dto(self, address): | ||
if address.startswith("KT"): | ||
return ["Originated", {"contract": self.owner, "entrypoint": None}] | ||
elif address.startswith("tz"): | ||
return ["Implicit", address] | ||
else: | ||
raise ValueError(f"Unsupported address: {address}") | ||
|
||
def to_dto(self): | ||
return [ | ||
"Operation_withdraw", | ||
{ | ||
"sender": self.sender, | ||
"owner": self.address_to_dto(self.owner), | ||
"amount": str(self.amount), | ||
"ticket_id": self.ticket_id.to_dto(), | ||
}, | ||
] | ||
|
||
|
||
@define | ||
class Noop: | ||
sender: Address = field() | ||
|
||
def to_dto(self): | ||
return ["Operation_noop", {"sender": self.sender}] | ||
|
||
|
||
@define | ||
class VMTransaction: | ||
sender: Address = field() | ||
operation: Any = field() # FIXME | ||
|
||
def to_dto(self): | ||
return [ | ||
"Operation_vm_transaction", | ||
{"sender": self.sender, "operation": self.operation}, | ||
] | ||
|
||
|
||
@define | ||
class HashedOperation: | ||
bytes_: bytes = field() | ||
hash_: bytes = field() | ||
nonce: int = field() | ||
level: int = field() | ||
operation: Any = field # TODO | ||
|
||
def to_dto(self): | ||
return [ | ||
"Initial_operation", | ||
{ | ||
"hash": self.hash_, | ||
"nonce": str(self.nonce), | ||
"level": str(self.level), | ||
"operation": self.operation.to_dto(), | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from attrs import define, field, validators | ||
from deku.core.ticket import TicketID | ||
from deku.core.address import Address | ||
from typing import List, Tuple | ||
from deku.utils import b58decode | ||
|
||
|
||
def bytes_converter(s: str) -> bytes: | ||
if isinstance(s, bytes): | ||
return s | ||
return bytes(s, encoding="utf-8") | ||
|
||
|
||
def from_str_couples(ls: List[Tuple[str, str]]): | ||
return [(bytes_converter(x), bytes_converter(y)) for (x, y) in ls] | ||
|
||
|
||
@define | ||
class Handle: | ||
id: int = field() | ||
owner: Address = field() | ||
ticket_id: TicketID = field() | ||
hash: bytes = field(converter=bytes_converter) | ||
amount: int = field(converter=int, validator=validators.gt(0)) | ||
|
||
def b58decode(self): | ||
assert self.hash.startswith(b"Dq"), ( | ||
"Needs to be called on a handle " + "with a b58-encoded hash" | ||
) | ||
hash = b58decode(self.hash) | ||
return Handle(self.id, self.owner, self.ticket_id, hash, self.amount) | ||
|
||
def to_dto(self): | ||
return { | ||
"amount": self.amount, | ||
"data": self.ticket_id.data, | ||
"id": self.id, | ||
"owner": self.owner, | ||
"ticketer": self.ticket_id.ticketer, | ||
} | ||
|
||
|
||
def handle_converter(h) -> Handle: | ||
if isinstance(h, Handle): | ||
return Handle(h.id, h.owner, h.ticket_id, h.hash, h.amount) | ||
return Handle(**h) | ||
|
||
|
||
@define | ||
class Proof: | ||
withdrawal_handles_hash: bytes = field(converter=bytes_converter) | ||
handle: Handle = field(converter=handle_converter) | ||
proof: List[Tuple[str, str]] = field(converter=from_str_couples) | ||
|
||
def b58decode(self): | ||
assert self.withdrawal_handles_hash.startswith( | ||
b"Dq" | ||
), "Needs to be called on a proof with b58-encoded hashes" | ||
withdrawal_handles_hash = b58decode(self.withdrawal_handles_hash) | ||
handle = self.handle.b58decode() | ||
proof = [(b58decode(x), b58decode(y)) for (x, y) in self.proof] | ||
return Proof(withdrawal_handles_hash, handle, proof) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from attrs import define, field, validators | ||
from deku.core.address import Address | ||
from typing import Union, Optional | ||
|
||
|
||
def check_data(self, attribute, value): | ||
return True # TODO | ||
|
||
|
||
def check_amount(self, attribute, value): | ||
return True # TODO | ||
|
||
|
||
@define | ||
class TicketID: | ||
ticketer: Address = field() | ||
data: bytes = field(validator=[validators.instance_of(bytes), check_data]) | ||
|
||
def to_dto(self): | ||
return ["Ticket_id", {"ticketer": self.ticketer, "data": self.data}] | ||
|
||
@classmethod | ||
def of_dto(cls, yojson_repr): | ||
assert yojson_repr[0] == "Ticket_id", f"Ill-formed DTO: {yojson_repr}" | ||
return TicketID( | ||
yojson_repr[1]["ticketer"], bytes(yojson_repr[1]["data"], encoding="utf-8") | ||
) | ||
|
||
|
||
@define(init=False) | ||
class Ticket: | ||
ticket_id: TicketID = field() | ||
amount: int = field() | ||
|
||
def __init__( | ||
self, | ||
ticket: Union[TicketID, str], | ||
data_or_amount: Union[bytes, int], | ||
amount: Optional[int] = None, | ||
): | ||
if isinstance(ticket, TicketID): | ||
self.ticket_id = ticket | ||
self.amount = data_or_amount | ||
else: | ||
self.ticket_id = TicketID(ticket, data_or_amount) | ||
self.amount = amount | ||
|
||
def with_amount(self, new_amount: int) -> "Ticket": | ||
return Ticket(self.ticket_id, new_amount) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
from .network.http import DekuAPI | ||
from .core.operation import Transfer, HashedOperation, Withdraw, VMTransaction | ||
from .core.address import Address | ||
from .core.ticket import TicketID | ||
from pytezos import PyTezosClient | ||
import deku.utils as utils | ||
import random | ||
from typing import Dict, Optional | ||
|
||
|
||
class DekuChain: | ||
deku_api: DekuAPI | ||
tezos_client: PyTezosClient | ||
|
||
def __init__(self, tezos_client=None, deku_api=None): | ||
if deku_api is None: | ||
self.deku_api = DekuAPI("https://deku-canonical-vm0.deku-v1.marigold.dev") | ||
else: | ||
self.deku_api = DekuAPI(deku_api) | ||
self.tezos_client = tezos_client | ||
|
||
def self_address(self): | ||
return self.tezos_client.key.public_key_hash() | ||
|
||
def info(self) -> Dict: | ||
return self.deku_api.info() | ||
|
||
def level(self) -> int: | ||
return self.deku_api.level() | ||
|
||
def balance(self, ticket: TicketID, address: Optional[Address] = None) -> int: | ||
if address is None: | ||
address = self.self_address() | ||
return self.deku_api.balance(ticket, address) | ||
|
||
def operation_options(self): | ||
level = self.level() | ||
nonce = random.randint(100000, 2**62 - 1) # OCaml max int on 64 bits | ||
return (level, nonce) | ||
|
||
# FIXME: right now endpoints are defined in http.py but we already have to | ||
# know the type of their arguments here | ||
def encode_operation(self, level, nonce, operation): | ||
return self.deku_api.encode_operation( | ||
str(nonce), str(level), operation.to_dto() | ||
) | ||
|
||
def submit_operation(self, level, nonce, transaction): | ||
bytes_ = self.encode_operation(level, nonce, transaction) | ||
hash_ = utils.b58encode(utils.blake2b(bytes_)) | ||
hashed_tx = HashedOperation(bytes_, hash_, nonce, level, transaction) | ||
key = self.tezos_client.key | ||
signature = key.sign(bytes_) | ||
signed_operation_dto = { | ||
"key": key.public_key(), | ||
"signature": signature, | ||
"initial": hashed_tx.to_dto(), | ||
} | ||
return self.deku_api.submit_operation(signed_operation_dto) | ||
|
||
# TODO: refactor those methods | ||
def transfer(self, receiver, ticket): | ||
(level, nonce) = self.operation_options() # TODO options | ||
sender = self.self_address() | ||
transaction = Transfer(sender, receiver, ticket.ticket_id, ticket.amount) | ||
return self.submit_operation(level, nonce, transaction)["hash"] | ||
|
||
def withdraw(self, tezos_account, ticket): | ||
(level, nonce) = self.operation_options() # TODO options | ||
sender = self.self_address() | ||
withdraw = Withdraw(sender, tezos_account, ticket.ticket_id, ticket.amount) | ||
return self.submit_operation(level, nonce, withdraw)["hash"] | ||
|
||
def send_vm_operation(self, vm_operation): | ||
(level, nonce) = self.operation_options() | ||
vm_tx = VMTransaction(self.self_address(), vm_operation) | ||
hash = self.submit_operation(level, nonce, vm_tx)["hash"] | ||
address = self.deku_api.compute_contract_address(hash) | ||
return (hash, address["address"]) | ||
|
||
def withdraw_proof(self, hash): | ||
return self.deku_api.proof(hash) | ||
|
||
def originate(self, source, initial_storage, lang_api): | ||
compiled = lang_api.compile_contract(source, initial_storage) | ||
# TODO: WASM, Ligo | ||
return self.send_vm_operation(compiled) | ||
|
||
def invoke(self, contract_address, argument, lang_api): | ||
compiled = lang_api.compile_invocation(contract_address, argument) | ||
# TODO: WASM, Ligo | ||
return self.send_vm_operation(compiled)[0] |
Empty file.
Oops, something went wrong.