Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating bindings for token contracts. #8

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions stellar_contract_bindings/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import dataclasses
from typing import List, Optional, Tuple, Type, Union

import xdrlib3
from stellar_sdk import xdr


Expand Down Expand Up @@ -108,3 +109,17 @@ def parse_entries(
offset += len(entry.to_xdr_bytes())
entries.append(entry)
return entries


def get_token_sc_spec_entry() -> list[xdr.SCSpecEntry]:
"""Get the token contract spec entry."""

# A little bit hacky, but it works
# TODO: find a way to get the token contract spec entry in the repo
# https://github.com/stellar/stellar-cli/blob/a11a924d310c1602e7b579377daa3e373010ac0e/cmd/soroban-cli/src/get_spec.rs#L77
raw_xdr = "AAAAAAAAAYpSZXR1cm5zIHRoZSBhbGxvd2FuY2UgZm9yIGBzcGVuZGVyYCB0byB0cmFuc2ZlciBmcm9tIGBmcm9tYC4KClRoZSBhbW91bnQgcmV0dXJuZWQgaXMgdGhlIGFtb3VudCB0aGF0IHNwZW5kZXIgaXMgYWxsb3dlZCB0byB0cmFuc2ZlcgpvdXQgb2YgZnJvbSdzIGJhbGFuY2UuIFdoZW4gdGhlIHNwZW5kZXIgdHJhbnNmZXJzIGFtb3VudHMsIHRoZSBhbGxvd2FuY2UKd2lsbCBiZSByZWR1Y2VkIGJ5IHRoZSBhbW91bnQgdHJhbnNmZXJyZWQuCgojIEFyZ3VtZW50cwoKKiBgZnJvbWAgLSBUaGUgYWRkcmVzcyBob2xkaW5nIHRoZSBiYWxhbmNlIG9mIHRva2VucyB0byBiZSBkcmF3biBmcm9tLgoqIGBzcGVuZGVyYCAtIFRoZSBhZGRyZXNzIHNwZW5kaW5nIHRoZSB0b2tlbnMgaGVsZCBieSBgZnJvbWAuAAAAAAAJYWxsb3dhbmNlAAAAAAAAAgAAAAAAAAAEZnJvbQAAABMAAAAAAAAAB3NwZW5kZXIAAAAAEwAAAAEAAAALAAAAAAAAAIlSZXR1cm5zIHRydWUgaWYgYGlkYCBpcyBhdXRob3JpemVkIHRvIHVzZSBpdHMgYmFsYW5jZS4KCiMgQXJndW1lbnRzCgoqIGBpZGAgLSBUaGUgYWRkcmVzcyBmb3Igd2hpY2ggdG9rZW4gYXV0aG9yaXphdGlvbiBpcyBiZWluZyBjaGVja2VkLgAAAAAAAAphdXRob3JpemVkAAAAAAABAAAAAAAAAAJpZAAAAAAAEwAAAAEAAAABAAAAAAAAA59TZXQgdGhlIGFsbG93YW5jZSBieSBgYW1vdW50YCBmb3IgYHNwZW5kZXJgIHRvIHRyYW5zZmVyL2J1cm4gZnJvbQpgZnJvbWAuCgpUaGUgYW1vdW50IHNldCBpcyB0aGUgYW1vdW50IHRoYXQgc3BlbmRlciBpcyBhcHByb3ZlZCB0byB0cmFuc2ZlciBvdXQgb2YKZnJvbSdzIGJhbGFuY2UuIFRoZSBzcGVuZGVyIHdpbGwgYmUgYWxsb3dlZCB0byB0cmFuc2ZlciBhbW91bnRzLCBhbmQKd2hlbiBhbiBhbW91bnQgaXMgdHJhbnNmZXJyZWQgdGhlIGFsbG93YW5jZSB3aWxsIGJlIHJlZHVjZWQgYnkgdGhlCmFtb3VudCB0cmFuc2ZlcnJlZC4KCiMgQXJndW1lbnRzCgoqIGBmcm9tYCAtIFRoZSBhZGRyZXNzIGhvbGRpbmcgdGhlIGJhbGFuY2Ugb2YgdG9rZW5zIHRvIGJlIGRyYXduIGZyb20uCiogYHNwZW5kZXJgIC0gVGhlIGFkZHJlc3MgYmVpbmcgYXV0aG9yaXplZCB0byBzcGVuZCB0aGUgdG9rZW5zIGhlbGQgYnkKYGZyb21gLgoqIGBhbW91bnRgIC0gVGhlIHRva2VucyB0byBiZSBtYWRlIGF2YWlsYWJsZSB0byBgc3BlbmRlcmAuCiogYGV4cGlyYXRpb25fbGVkZ2VyYCAtIFRoZSBsZWRnZXIgbnVtYmVyIHdoZXJlIHRoaXMgYWxsb3dhbmNlIGV4cGlyZXMuIENhbm5vdApiZSBsZXNzIHRoYW4gdGhlIGN1cnJlbnQgbGVkZ2VyIG51bWJlciB1bmxlc3MgdGhlIGFtb3VudCBpcyBiZWluZyBzZXQgdG8gMC4KQW4gZXhwaXJlZCBlbnRyeSAod2hlcmUgZXhwaXJhdGlvbl9sZWRnZXIgPCB0aGUgY3VycmVudCBsZWRnZXIgbnVtYmVyKQpzaG91bGQgYmUgdHJlYXRlZCBhcyBhIDAgYW1vdW50IGFsbG93YW5jZS4KCiMgRXZlbnRzCgpFbWl0cyBhbiBldmVudCB3aXRoIHRvcGljcyBgWyJhcHByb3ZlIiwgZnJvbTogQWRkcmVzcywKc3BlbmRlcjogQWRkcmVzc10sIGRhdGEgPSBbYW1vdW50OiBpMTI4LCBleHBpcmF0aW9uX2xlZGdlcjogdTMyXWAAAAAAB2FwcHJvdmUAAAAABAAAAAAAAAAEZnJvbQAAABMAAAAAAAAAB3NwZW5kZXIAAAAAEwAAAAAAAAAGYW1vdW50AAAAAAALAAAAAAAAABFleHBpcmF0aW9uX2xlZGdlcgAAAAAAAAQAAAAAAAAAAAAAAJhSZXR1cm5zIHRoZSBiYWxhbmNlIG9mIGBpZGAuCgojIEFyZ3VtZW50cwoKKiBgaWRgIC0gVGhlIGFkZHJlc3MgZm9yIHdoaWNoIGEgYmFsYW5jZSBpcyBiZWluZyBxdWVyaWVkLiBJZiB0aGUKYWRkcmVzcyBoYXMgbm8gZXhpc3RpbmcgYmFsYW5jZSwgcmV0dXJucyAwLgAAAAdiYWxhbmNlAAAAAAEAAAAAAAAAAmlkAAAAAAATAAAAAQAAAAsAAAAAAAABYkJ1cm4gYGFtb3VudGAgZnJvbSBgZnJvbWAuCgpSZWR1Y2VzIGZyb20ncyBiYWxhbmNlIGJ5IHRoZSBhbW91bnQsIHdpdGhvdXQgdHJhbnNmZXJyaW5nIHRoZSBiYWxhbmNlCnRvIGFub3RoZXIgaG9sZGVyJ3MgYmFsYW5jZS4KCiMgQXJndW1lbnRzCgoqIGBmcm9tYCAtIFRoZSBhZGRyZXNzIGhvbGRpbmcgdGhlIGJhbGFuY2Ugb2YgdG9rZW5zIHdoaWNoIHdpbGwgYmUKYnVybmVkIGZyb20uCiogYGFtb3VudGAgLSBUaGUgYW1vdW50IG9mIHRva2VucyB0byBiZSBidXJuZWQuCgojIEV2ZW50cwoKRW1pdHMgYW4gZXZlbnQgd2l0aCB0b3BpY3MgYFsiYnVybiIsIGZyb206IEFkZHJlc3NdLCBkYXRhID0gYW1vdW50OgppMTI4YAAAAAAABGJ1cm4AAAACAAAAAAAAAARmcm9tAAAAEwAAAAAAAAAGYW1vdW50AAAAAAALAAAAAAAAAAAAAALaQnVybiBgYW1vdW50YCBmcm9tIGBmcm9tYCwgY29uc3VtaW5nIHRoZSBhbGxvd2FuY2Ugb2YgYHNwZW5kZXJgLgoKUmVkdWNlcyBmcm9tJ3MgYmFsYW5jZSBieSB0aGUgYW1vdW50LCB3aXRob3V0IHRyYW5zZmVycmluZyB0aGUgYmFsYW5jZQp0byBhbm90aGVyIGhvbGRlcidzIGJhbGFuY2UuCgpUaGUgc3BlbmRlciB3aWxsIGJlIGFsbG93ZWQgdG8gYnVybiB0aGUgYW1vdW50IGZyb20gZnJvbSdzIGJhbGFuY2UsIGlmCnRoZSBhbW91bnQgaXMgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHRoZSBhbGxvd2FuY2UgdGhhdCB0aGUgc3BlbmRlciBoYXMKb24gdGhlIGZyb20ncyBiYWxhbmNlLiBUaGUgc3BlbmRlcidzIGFsbG93YW5jZSBvbiBmcm9tJ3MgYmFsYW5jZSB3aWxsIGJlCnJlZHVjZWQgYnkgdGhlIGFtb3VudC4KCiMgQXJndW1lbnRzCgoqIGBzcGVuZGVyYCAtIFRoZSBhZGRyZXNzIGF1dGhvcml6aW5nIHRoZSBidXJuLCBhbmQgaGF2aW5nIGl0cyBhbGxvd2FuY2UKY29uc3VtZWQgZHVyaW5nIHRoZSBidXJuLgoqIGBmcm9tYCAtIFRoZSBhZGRyZXNzIGhvbGRpbmcgdGhlIGJhbGFuY2Ugb2YgdG9rZW5zIHdoaWNoIHdpbGwgYmUKYnVybmVkIGZyb20uCiogYGFtb3VudGAgLSBUaGUgYW1vdW50IG9mIHRva2VucyB0byBiZSBidXJuZWQuCgojIEV2ZW50cwoKRW1pdHMgYW4gZXZlbnQgd2l0aCB0b3BpY3MgYFsiYnVybiIsIGZyb206IEFkZHJlc3NdLCBkYXRhID0gYW1vdW50OgppMTI4YAAAAAAACWJ1cm5fZnJvbQAAAAAAAAMAAAAAAAAAB3NwZW5kZXIAAAAAEwAAAAAAAAAEZnJvbQAAABMAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAAAAAABUUNsYXdiYWNrIGBhbW91bnRgIGZyb20gYGZyb21gIGFjY291bnQuIGBhbW91bnRgIGlzIGJ1cm5lZCBpbiB0aGUKY2xhd2JhY2sgcHJvY2Vzcy4KCiMgQXJndW1lbnRzCgoqIGBmcm9tYCAtIFRoZSBhZGRyZXNzIGhvbGRpbmcgdGhlIGJhbGFuY2UgZnJvbSB3aGljaCB0aGUgY2xhd2JhY2sgd2lsbAp0YWtlIHRva2Vucy4KKiBgYW1vdW50YCAtIFRoZSBhbW91bnQgb2YgdG9rZW5zIHRvIGJlIGNsYXdlZCBiYWNrLgoKIyBFdmVudHMKCkVtaXRzIGFuIGV2ZW50IHdpdGggdG9waWNzIGBbImNsYXdiYWNrIiwgYWRtaW46IEFkZHJlc3MsIHRvOiBBZGRyZXNzXSwKZGF0YSA9IGFtb3VudDogaTEyOGAAAAAAAAAIY2xhd2JhY2sAAAACAAAAAAAAAARmcm9tAAAAEwAAAAAAAAAGYW1vdW50AAAAAAALAAAAAAAAAAAAAACAUmV0dXJucyB0aGUgbnVtYmVyIG9mIGRlY2ltYWxzIHVzZWQgdG8gcmVwcmVzZW50IGFtb3VudHMgb2YgdGhpcyB0b2tlbi4KCiMgUGFuaWNzCgpJZiB0aGUgY29udHJhY3QgaGFzIG5vdCB5ZXQgYmVlbiBpbml0aWFsaXplZC4AAAAIZGVjaW1hbHMAAAAAAAAAAQAAAAQAAAAAAAAA801pbnRzIGBhbW91bnRgIHRvIGB0b2AuCgojIEFyZ3VtZW50cwoKKiBgdG9gIC0gVGhlIGFkZHJlc3Mgd2hpY2ggd2lsbCByZWNlaXZlIHRoZSBtaW50ZWQgdG9rZW5zLgoqIGBhbW91bnRgIC0gVGhlIGFtb3VudCBvZiB0b2tlbnMgdG8gYmUgbWludGVkLgoKIyBFdmVudHMKCkVtaXRzIGFuIGV2ZW50IHdpdGggdG9waWNzIGBbIm1pbnQiLCBhZG1pbjogQWRkcmVzcywgdG86IEFkZHJlc3NdLCBkYXRhCj0gYW1vdW50OiBpMTI4YAAAAAAEbWludAAAAAIAAAAAAAAAAnRvAAAAAAATAAAAAAAAAAZhbW91bnQAAAAAAAsAAAAAAAAAAAAAAFlSZXR1cm5zIHRoZSBuYW1lIGZvciB0aGlzIHRva2VuLgoKIyBQYW5pY3MKCklmIHRoZSBjb250cmFjdCBoYXMgbm90IHlldCBiZWVuIGluaXRpYWxpemVkLgAAAAAAAARuYW1lAAAAAAAAAAEAAAAQAAAAAAAAAQxTZXRzIHRoZSBhZG1pbmlzdHJhdG9yIHRvIHRoZSBzcGVjaWZpZWQgYWRkcmVzcyBgbmV3X2FkbWluYC4KCiMgQXJndW1lbnRzCgoqIGBuZXdfYWRtaW5gIC0gVGhlIGFkZHJlc3Mgd2hpY2ggd2lsbCBoZW5jZWZvcnRoIGJlIHRoZSBhZG1pbmlzdHJhdG9yCm9mIHRoaXMgdG9rZW4gY29udHJhY3QuCgojIEV2ZW50cwoKRW1pdHMgYW4gZXZlbnQgd2l0aCB0b3BpY3MgYFsic2V0X2FkbWluIiwgYWRtaW46IEFkZHJlc3NdLCBkYXRhID0KW25ld19hZG1pbjogQWRkcmVzc11gAAAACXNldF9hZG1pbgAAAAAAAAEAAAAAAAAACW5ld19hZG1pbgAAAAAAABMAAAAAAAAAAAAAAEZSZXR1cm5zIHRoZSBhZG1pbiBvZiB0aGUgY29udHJhY3QuCgojIFBhbmljcwoKSWYgdGhlIGFkbWluIGlzIG5vdCBzZXQuAAAAAAAFYWRtaW4AAAAAAAAAAAAAAQAAABMAAAAAAAABUFNldHMgd2hldGhlciB0aGUgYWNjb3VudCBpcyBhdXRob3JpemVkIHRvIHVzZSBpdHMgYmFsYW5jZS4gSWYKYGF1dGhvcml6ZWRgIGlzIHRydWUsIGBpZGAgc2hvdWxkIGJlIGFibGUgdG8gdXNlIGl0cyBiYWxhbmNlLgoKIyBBcmd1bWVudHMKCiogYGlkYCAtIFRoZSBhZGRyZXNzIGJlaW5nIChkZS0pYXV0aG9yaXplZC4KKiBgYXV0aG9yaXplYCAtIFdoZXRoZXIgb3Igbm90IGBpZGAgY2FuIHVzZSBpdHMgYmFsYW5jZS4KCiMgRXZlbnRzCgpFbWl0cyBhbiBldmVudCB3aXRoIHRvcGljcyBgWyJzZXRfYXV0aG9yaXplZCIsIGlkOiBBZGRyZXNzXSwgZGF0YSA9ClthdXRob3JpemU6IGJvb2xdYAAAAA5zZXRfYXV0aG9yaXplZAAAAAAAAgAAAAAAAAACaWQAAAAAABMAAAAAAAAACWF1dGhvcml6ZQAAAAAAAAEAAAAAAAAAAAAAAFtSZXR1cm5zIHRoZSBzeW1ib2wgZm9yIHRoaXMgdG9rZW4uCgojIFBhbmljcwoKSWYgdGhlIGNvbnRyYWN0IGhhcyBub3QgeWV0IGJlZW4gaW5pdGlhbGl6ZWQuAAAAAAZzeW1ib2wAAAAAAAAAAAABAAAAEAAAAAAAAAFiVHJhbnNmZXIgYGFtb3VudGAgZnJvbSBgZnJvbWAgdG8gYHRvYC4KCiMgQXJndW1lbnRzCgoqIGBmcm9tYCAtIFRoZSBhZGRyZXNzIGhvbGRpbmcgdGhlIGJhbGFuY2Ugb2YgdG9rZW5zIHdoaWNoIHdpbGwgYmUKd2l0aGRyYXduIGZyb20uCiogYHRvYCAtIFRoZSBhZGRyZXNzIHdoaWNoIHdpbGwgcmVjZWl2ZSB0aGUgdHJhbnNmZXJyZWQgdG9rZW5zLgoqIGBhbW91bnRgIC0gVGhlIGFtb3VudCBvZiB0b2tlbnMgdG8gYmUgdHJhbnNmZXJyZWQuCgojIEV2ZW50cwoKRW1pdHMgYW4gZXZlbnQgd2l0aCB0b3BpY3MgYFsidHJhbnNmZXIiLCBmcm9tOiBBZGRyZXNzLCB0bzogQWRkcmVzc10sCmRhdGEgPSBhbW91bnQ6IGkxMjhgAAAAAAAIdHJhbnNmZXIAAAADAAAAAAAAAARmcm9tAAAAEwAAAAAAAAACdG8AAAAAABMAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAAAAAADMVRyYW5zZmVyIGBhbW91bnRgIGZyb20gYGZyb21gIHRvIGB0b2AsIGNvbnN1bWluZyB0aGUgYWxsb3dhbmNlIHRoYXQKYHNwZW5kZXJgIGhhcyBvbiBgZnJvbWAncyBiYWxhbmNlLiBBdXRob3JpemVkIGJ5IHNwZW5kZXIKKGBzcGVuZGVyLnJlcXVpcmVfYXV0aCgpYCkuCgpUaGUgc3BlbmRlciB3aWxsIGJlIGFsbG93ZWQgdG8gdHJhbnNmZXIgdGhlIGFtb3VudCBmcm9tIGZyb20ncyBiYWxhbmNlCmlmIHRoZSBhbW91bnQgaXMgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHRoZSBhbGxvd2FuY2UgdGhhdCB0aGUgc3BlbmRlcgpoYXMgb24gdGhlIGZyb20ncyBiYWxhbmNlLiBUaGUgc3BlbmRlcidzIGFsbG93YW5jZSBvbiBmcm9tJ3MgYmFsYW5jZQp3aWxsIGJlIHJlZHVjZWQgYnkgdGhlIGFtb3VudC4KCiMgQXJndW1lbnRzCgoqIGBzcGVuZGVyYCAtIFRoZSBhZGRyZXNzIGF1dGhvcml6aW5nIHRoZSB0cmFuc2ZlciwgYW5kIGhhdmluZyBpdHMKYWxsb3dhbmNlIGNvbnN1bWVkIGR1cmluZyB0aGUgdHJhbnNmZXIuCiogYGZyb21gIC0gVGhlIGFkZHJlc3MgaG9sZGluZyB0aGUgYmFsYW5jZSBvZiB0b2tlbnMgd2hpY2ggd2lsbCBiZQp3aXRoZHJhd24gZnJvbS4KKiBgdG9gIC0gVGhlIGFkZHJlc3Mgd2hpY2ggd2lsbCByZWNlaXZlIHRoZSB0cmFuc2ZlcnJlZCB0b2tlbnMuCiogYGFtb3VudGAgLSBUaGUgYW1vdW50IG9mIHRva2VucyB0byBiZSB0cmFuc2ZlcnJlZC4KCiMgRXZlbnRzCgpFbWl0cyBhbiBldmVudCB3aXRoIHRvcGljcyBgWyJ0cmFuc2ZlciIsIGZyb206IEFkZHJlc3MsIHRvOiBBZGRyZXNzXSwKZGF0YSA9IGFtb3VudDogaTEyOGAAAAAAAAANdHJhbnNmZXJfZnJvbQAAAAAAAAQAAAAAAAAAB3NwZW5kZXIAAAAAEwAAAAAAAAAEZnJvbQAAABMAAAAAAAAAAnRvAAAAAAATAAAAAAAAAAZhbW91bnQAAAAAAAsAAAAA"
unpacker = xdrlib3.Unpacker(base64.b64decode(raw_xdr))
specs = []
while unpacker.get_position() < len(base64.b64decode(raw_xdr)):
specs.append(xdr.SCSpecEntry.unpack(unpacker))
return specs
103 changes: 79 additions & 24 deletions stellar_contract_bindings/python.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import keyword
import os
from typing import List

Expand All @@ -7,11 +8,7 @@
from stellar_sdk import xdr

from stellar_contract_bindings import __version__ as stellar_contract_bindings_version
from stellar_contract_bindings.metadata import parse_contract_metadata
from stellar_contract_bindings.utils import (
get_wasm_hash_by_contract_id,
get_contract_wasm_by_hash,
)
from stellar_contract_bindings.utils import get_specs_by_contract_id


def is_tuple_struct(entry: xdr.SCSpecUDTStructV0) -> bool:
Expand Down Expand Up @@ -300,7 +297,7 @@ def __init__(self, {% for field in entry.fields %}{{ field.name.decode() }}: {{
def to_scval(self) -> xdr.SCVal:
return scval.to_struct({
{%- for field in entry.fields %}
'{{ field.name.decode() }}': {{ to_scval(field.type, 'self.' ~ field.name.decode()) }}{% if not loop.last %},{% endif %}
'{{ field.name_r.decode() if field.name_r else field.name.decode() }}': {{ to_scval(field.type, 'self.' ~ field.name.decode()) }}{% if not loop.last %},{% endif %}
{%- endfor %}
})

Expand All @@ -309,7 +306,7 @@ def from_scval(cls, val: xdr.SCVal):
elements = scval.from_struct(val)
return cls(
{%- for index, field in enumerate(entry.fields) %}
{{ from_scval(field.type, 'elements["' ~ field.name.decode() ~ '"]') }}{% if not loop.last %},{% endif %}
{{ from_scval(field.type, 'elements["' ~ (field.name_r.decode() if field.name_r else field.name.decode()) ~ '"]') }}{% if not loop.last %},{% endif %}
{%- endfor %}
)

Expand Down Expand Up @@ -371,9 +368,9 @@ def render_union(entry: xdr.SCSpecUDTUnionV0):
class {{ entry.name.decode() }}Kind(Enum):
{%- for case in entry.cases %}
{%- if case.kind == xdr.SCSpecUDTUnionCaseV0Kind.SC_SPEC_UDT_UNION_CASE_VOID_V0 %}
{{ case.void_case.name.decode() }} = '{{ case.void_case.name.decode() }}'
{{ case.void_case.name.decode() }} = '{{ case.void_case.name_r.decode() if case.void_case.name_r else case.void_case.name.decode() }}'
{%- else %}
{{ case.tuple_case.name.decode() }} = '{{ case.tuple_case.name.decode() }}'
{{ case.tuple_case.name.decode() }} = '{{ case.tuple_case.name.decode() if case.tuple_case.name_r else case.tuple_case.name.decode() }}'
{%- endif %}
{%- endfor %}
"""
Expand Down Expand Up @@ -494,7 +491,7 @@ def {{ entry.name.sc_symbol.decode() }}(self, {% for param in entry.inputs %}{{
{%- if entry.doc %}
"""{{ entry.doc.decode() }}"""
{%- endif %}
return self.invoke('{{ entry.name.sc_symbol.decode() }}', [{% for param in entry.inputs %}{{ to_scval(param.type, param.name.decode()) }}{% if not loop.last %}, {% endif %}{% endfor %}], parse_result_xdr_fn={{ parse_result_xdr_fn(entry.outputs) }}, source = source, signer = signer, base_fee = base_fee, transaction_timeout = transaction_timeout, submit_timeout = submit_timeout, simulate = simulate, restore = restore)
return self.invoke('{{ entry.name.sc_symbol_r.decode() if entry.name.sc_symbol_r else entry.name.sc_symbol.decode() }}', [{% for param in entry.inputs %}{{ to_scval(param.type, param.name.decode()) }}{% if not loop.last %}, {% endif %}{% endfor %}], parse_result_xdr_fn={{ parse_result_xdr_fn(entry.outputs) }}, source = source, signer = signer, base_fee = base_fee, transaction_timeout = transaction_timeout, submit_timeout = submit_timeout, simulate = simulate, restore = restore)
{%- endfor %}
{%- endif %}

Expand All @@ -505,7 +502,7 @@ async def {{ entry.name.sc_symbol.decode() }}(self, {% for param in entry.inputs
{%- if entry.doc %}
"""{{ entry.doc.decode() }}"""
{%- endif %}
return await self.invoke('{{ entry.name.sc_symbol.decode() }}', [{% for param in entry.inputs %}{{ to_scval(param.type, param.name.decode()) }}{% if not loop.last %}, {% endif %}{% endfor %}], parse_result_xdr_fn={{ parse_result_xdr_fn(entry.outputs) }}, source = source, signer = signer, base_fee = base_fee, transaction_timeout = transaction_timeout, submit_timeout = submit_timeout, simulate = simulate, restore = restore)
return await self.invoke('{{ entry.name.sc_symbol_r.decode() if entry.name.sc_symbol_r else entry.name.sc_symbol.decode() }}', [{% for param in entry.inputs %}{{ to_scval(param.type, param.name.decode()) }}{% if not loop.last %}, {% endif %}{% endfor %}], parse_result_xdr_fn={{ parse_result_xdr_fn(entry.outputs) }}, source = source, signer = signer, base_fee = base_fee, transaction_timeout = transaction_timeout, submit_timeout = submit_timeout, simulate = simulate, restore = restore)
{%- endfor %}
{%- endif %}
'''
Expand Down Expand Up @@ -539,13 +536,70 @@ def parse_result_xdr_fn(output: List[xdr.SCSpecTypeDef]):
return client_rendered_code


def generate_binding(wasm: bytes, client_type: str) -> str:
generated = []
generated.append(render_info())
# append _ to keyword
def append_underscore(specs: List[xdr.SCSpecEntry]):
for spec in specs:
if spec.kind == xdr.SCSpecEntryKind.SC_SPEC_ENTRY_UDT_STRUCT_V0:
assert spec.udt_struct_v0 is not None
if keyword.iskeyword(spec.udt_struct_v0.name.decode()):
spec.udt_struct_v0.name_r = spec.udt_struct_v0.name # type: ignore[attr-defined]
spec.udt_struct_v0.name = spec.udt_struct_v0.name + b"_"
for field in spec.udt_struct_v0.fields:
if keyword.iskeyword(field.name.decode()):
field.name = field.name + b"_"
if spec.kind == xdr.SCSpecEntryKind.SC_SPEC_ENTRY_UDT_UNION_V0:
assert spec.udt_union_v0 is not None
if keyword.iskeyword(spec.udt_union_v0.name.decode()):
spec.udt_union_v0.name_r = spec.udt_union_v0.name # type: ignore[attr-defined]
spec.udt_union_v0.name = spec.udt_union_v0.name + b"_"
for union_case in spec.udt_union_v0.cases:
if (
union_case.kind
== xdr.SCSpecUDTUnionCaseV0Kind.SC_SPEC_UDT_UNION_CASE_TUPLE_V0
):
if keyword.iskeyword(union_case.tuple_case.name.decode()):
union_case.tuple_case.name_r = union_case.tuple_case.name # type: ignore[attr-defined]
union_case.tuple_case.name = union_case.tuple_case.name + b"_"
elif (
union_case.kind
== xdr.SCSpecUDTUnionCaseV0Kind.SC_SPEC_UDT_UNION_CASE_VOID_V0
):
if keyword.iskeyword(union_case.void_case.name.decode()):
union_case.void_case.name_r = union_case.void_case.name # type: ignore[attr-defined]
union_case.void_case.name = union_case.void_case.name + b"_"
else:
raise ValueError(f"Unsupported union case kind: {union_case.kind}")
if spec.kind == xdr.SCSpecEntryKind.SC_SPEC_ENTRY_FUNCTION_V0:
assert spec.function_v0 is not None
if keyword.iskeyword(spec.function_v0.name.sc_symbol.decode()):
spec.function_v0.name.sc_symbol_r = spec.function_v0.name.sc_symbol # type: ignore[attr-defined]
spec.function_v0.name.sc_symbol = spec.function_v0.name.sc_symbol + b"_"
for param in spec.function_v0.inputs:
if keyword.iskeyword(param.name.decode()):
param.name = param.name + b"_"
if spec.kind == xdr.SCSpecEntryKind.SC_SPEC_ENTRY_UDT_ENUM_V0:
assert spec.udt_enum_v0 is not None
if keyword.iskeyword(spec.udt_enum_v0.name.decode()):
spec.udt_enum_v0.name_r = spec.udt_enum_v0.name # type: ignore[attr-defined]
spec.udt_enum_v0.name = spec.udt_enum_v0.name + b"_"
for enum_case in spec.udt_enum_v0.cases:
if keyword.iskeyword(enum_case.name.decode()):
enum_case.name = enum_case.name + b"_"
if spec.kind == xdr.SCSpecEntryKind.SC_SPEC_ENTRY_UDT_ERROR_ENUM_V0:
assert spec.udt_error_enum_v0 is not None
if keyword.iskeyword(spec.udt_error_enum_v0.name.decode()):
spec.udt_error_enum_v0.name_r = spec.udt_error_enum_v0.name # type: ignore[attr-defined]
spec.udt_error_enum_v0.name = spec.udt_error_enum_v0.name + b"_"
for error_enum_case in spec.udt_error_enum_v0.cases:
if keyword.iskeyword(error_enum_case.name.decode()):
error_enum_case.name = error_enum_case.name + b"_"


metadata = parse_contract_metadata(wasm)
specs = metadata.spec
def generate_binding(specs: List[xdr.SCSpecEntry], client_type: str) -> str:
append_underscore(specs)

generated = []
generated.append(render_info())
generated.append(render_imports(client_type))

for spec in specs:
Expand Down Expand Up @@ -599,16 +653,13 @@ def command(contract_id: str, rpc_url: str, output: str, client_type: str):
if output is None:
output = os.getcwd()
try:
wasm_id = get_wasm_hash_by_contract_id(contract_id, rpc_url)
click.echo(f"Got wasm id: {wasm_id.hex()}")
wasm_code = get_contract_wasm_by_hash(wasm_id, rpc_url)
click.echo(f"Got wasm code")
specs = get_specs_by_contract_id(contract_id, rpc_url)
except Exception as e:
click.echo(f"Error: {str(e)}", err=True)
click.echo(f"Get contract specs failed: {e}", err=True)
raise click.Abort()

click.echo("Generating Python bindings")
generated = generate_binding(wasm_code, client_type=client_type)
generated = generate_binding(specs, client_type=client_type)
if not os.path.exists(output):
os.makedirs(output)
output_path = os.path.join(output, "bindings.py")
Expand All @@ -621,8 +672,12 @@ def command(contract_id: str, rpc_url: str, output: str, client_type: str):


if __name__ == "__main__":
from stellar_contract_bindings.metadata import parse_contract_metadata

wasm_file = "/Users/overcat/repo/lightsail/stellar-contract-bindings/tests/contracts/target/wasm32-unknown-unknown/release/python.wasm"
with open(wasm_file, "rb") as f:
wasm = f.read()
generated_code = generate_binding(wasm, "both")
print(generated_code)

specs = parse_contract_metadata(wasm).spec
generated = generate_binding(specs, client_type="both")
print(generated)
Loading