Bahasa GCULpy

GCULpy adalah subkumpulan Python yang diketik secara statis dan ketat, yang dirancang agar aman, mudah dibaca, dan dapat diaudit. Desainnya sengaja membatasi fitur dinamis tertentu Python untuk mencegah kerentanan smart contract umum dan memastikan perilaku kontrak selalu dapat diprediksi.

Halaman ini memberikan referensi untuk spesifikasi bahasa GCULpy, yang mencakup konsep inti dan siklus proses kontrak di jaringan Universal Ledger.

Konsep inti

Kontrak berikut menentukan contoh token ERC20 di GCULpy:

import gcul

class ERC20Token(gcul.Contract):
    """Sample ERC20 implementation for the Universal Ledger."""

    symbol: str
    total_supply: int
    balance: dict[gcul.Account, int]

    def __init__(self, symbol: str):
        self.symbol = symbol

    def mint(self, beneficiary: gcul.Account, value: int) -> int:
        """Mints tokens to the given beneficiary."""
        assert self.is_owner(gcul.sender), "Only the owner can mint"
        assert value >= 0, "Mint amount must be non-negative"
        self.total_supply += value
        self.balance[beneficiary] += value
        return value

    def transfer(self, beneficiary: gcul.Account, value: int) -> int:
        """Transfers tokens from the sender to the given beneficiary."""
        assert value >= 0, "Transfer amount must be non-negative"
        assert (
            value <= self.balance[gcul.sender]
        ), "Sender does not have enough balance"
        self.balance[gcul.sender] -= value
        self.balance[beneficiary] += value
        return value

Kontrak GCULpy adalah class yang diturunkan dari gcul.Contract. Objek berisi kolom (menyimpan status) dan metode (memproses logika yang beroperasi pada kolom).

Kolom

Status dalam kontrak GCULpy disimpan dalam kolom. Semua kolom harus dideklarasikan dengan jenis statis di tingkat class. Ada dua jenis kolom:

  • Kolom kontrak menyimpan satu nilai yang disimpan bersama kontrak itu sendiri. Dalam contoh ERC20Token, symbol: str dan total_supply: int adalah kolom kontrak.

  • Kolom akun menyimpan nilai terpisah untuk setiap akun pengguna yang berinteraksi dengan kontrak. Objek ini selalu dideklarasikan sebagai kamus (dict) dengan gcul.Account sebagai kunci, seperti balance: dict[gcul.Account, int]. Sebelum kontrak dapat menulis ke akun pengguna, pengguna harus memberikan izin penyimpanan secara eksplisit ke kontrak. Setelah data disimpan, hanya instance kontrak yang dapat mengubah atau menghapusnya—pengguna tidak dapat melakukannya.

Metode

Metode menentukan logika yang dapat dieksekusi dari kontrak. Fungsi ini berperilaku seperti metode Python dan dapat membaca atau mengubah kolom kontrak.

  • __init__: Konstruktor hanya dipanggil sekali saat kontrak pertama kali di-deploy. Digunakan untuk menetapkan status awal kolom kontrak. Kolom yang tidak diberi nilai dalam konstruktor akan mendapatkan default yang sesuai, misalnya 0 untuk kolom int atau kamus kosong untuk kolom dict.

  • Metode pribadi: Metode yang dimulai dengan garis bawah (misalnya, _internal_logic) bersifat pribadi dan hanya dapat dipanggil oleh metode lain dalam kontrak yang sama. Interpreter Universal Ledger memberlakukan batasan ini.

  • Metode publik: Metode apa pun yang tidak diawali dengan garis bawah (_) adalah publik. Metode publik dapat dipanggil oleh pengguna mana pun dengan ROLE_CONTRACT_PARTICIPANT dengan mengirimkan transaksi InvokeContractMethod.

Siklus proses kontrak

Bagian berikut menjelaskan operasi umum yang terlibat dalam siklus proses kontrak GCULpy.

Men-deploy kontrak

Pertama, kompilasi kode sumber GCULpy Anda menggunakan compiler gculpyc. Kemudian, pengguna dengan ROLE_CONTRACT_CREATOR dapat mengirimkan transaksi CreateContract untuk men-deploy bytecode yang dikompilasi ke jaringan Universal Ledger. Untuk petunjuk mendetail, lihat tutorial Men-deploy kontrak yang dapat diprogram.

Transaksi tersebut akan terlihat seperti:

client_transaction {
  sender_id: "OWNER_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.CreateContract] {
      contract_bytes: "COMPILED_BYTECODE"
      arguments {
        key: "symbol"
        value: { str_value: "US02079K1079" }
      }
    }
  }
}

Saat jaringan memproses transaksi ini:

  • Konstruktor, yaitu metode __init__, dieksekusi untuk membuat instance kontrak baru.
  • Pengirim transaksi menjadi pemilik kontrak.
  • Instance kontrak disimpan secara permanen dalam buku besar dan diberi ID kontrak unik yang ditampilkan sebagai bagian dari output transaksi.

Memberikan izin

Sebelum kontrak dapat menyimpan data di kolom akun atas nama pengguna, pengguna harus memberikan izin penyimpanan terlebih dahulu. Ini adalah langkah keamanan yang sangat penting. Pengguna dengan ROLE_CONTRACT_PARTICIPANT dapat mengirimkan transaksi GrantContractPermissions untuk ID kontrak tertentu.

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
      contract_id: "CONTRACT_ID"
      permissions: CONTRACT_PERMISSION_STORAGE
    }
  }
}

Saat jaringan memproses transaksi ini:

  • Jika kontrak tidak menentukan kolom akun apa pun, transaksi akan ditolak.
  • Jika kontrak menentukan kolom akun, semuanya akan diisi dengan nilai default (misalnya, contract.balance[gcul.sender] = 0). Nilai ini kemudian disimpan dalam status dunia sebagai bagian dari data akun, dan pengirim transaksi terdaftar sebagai peserta dengan instance kontrak tertentu ini.

Memanggil metode kontrak

Setelah kontrak di-deploy dan izin yang diperlukan diberikan, pengguna dapat berinteraksi dengannya dengan memanggil metode publiknya. Pengguna dengan ROLE_CONTRACT_PARTICIPANT dapat mengirimkan transaksi InvokeContractMethod yang menentukan ID kontrak, nama metode, dan nilai argumen.

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.InvokeContractMethod] {
      contract_id: "CONTRACT_ID"
      method_name: "mint"
      arguments {
        key: "beneficiary"
        value: { account_id: "BENEFICIARY_ID" }
      }
      arguments {
        key: "value"
        value: { int_value: 10 }
      }
    }
  }
}

Saat jaringan memproses transaksi ini:

  • Instance kontrak yang terkait dengan CONTRACT_ID yang diberikan akan diambil.
  • Metode mint(beneficiary=Account("BENEFICIARY_ID"), value=10) dijalankan. Objek Account untuk penerima manfaat dibuat dan divalidasi oleh runtime. Logika metode dapat dengan aman mengasumsikan bahwa ID yang diberikan valid dan merujuk ke akun yang ada di buku besar.
  • Jika metode gagal karena alasan apa pun, transaksi akan gagal dan tidak ada pembaruan yang akan dilakukan pada status kontrak.
  • Jika metode berhasil, status kontrak yang diperbarui akan dicatat dalam status dunia.

Spesifikasi bahasa

GCULpy dirancang untuk keamanan dan prediktabilitas dan, oleh karena itu, tidak mengizinkan beberapa fitur Python; fitur ini akan ditandai dengan label Pembatasan. Batasan ini dimaksudkan untuk menjadi fitur bahasa permanen, yang diperkenalkan untuk membuat logika kontrak lebih mudah dibaca, diaudit, dan dianalisis secara statis, sehingga membatasi perilaku yang mengejutkan atau tidak aman.

Fitur lainnya ditandai dengan label Roadmap, yang berarti fitur tersebut ada dalam peta jalan penerapan, tetapi belum didukung oleh compiler gculpyc.

Jenis

GCULpy mendukung berbagai jenis variabel umum dengan penekanan yang kuat pada pengetikan statis.

Jenis nilai utama:

  • int, bool, str, None sudah didukung.
  • Roadmap Decimal, bytes, Enum ada dalam roadmap.
  • Pembatasan float dan complex tidak diizinkan.

Jenis penampung:

  • dict sudah didukung.
  • Roadmap list, tuple, set, dataclass ada dalam roadmap.
  • Batasan Jenis konkret harus ditentukan untuk nilai dalam penampung, misalnya dict[str, int] diizinkan, tetapi dict atau dict[str, Any] biasa tidak diizinkan.
  • Penyusunan penampung didukung, misalnya dict[str, list[int]].

Batasan Semua variabel—termasuk kolom kontrak dan akun, parameter fungsi dan jenis nilai yang ditampilkan—harus ditentukan dan diketik secara statis. Jenisnya tidak dapat diubah saat runtime dan hanya jenis konkret yang didukung. Jenis tidak dapat digunakan sebagai nilai, misalnya, jenis tidak dapat disimpan dalam variabel atau diteruskan ke fungsi sebagai argumen. Mencoba menetapkan nilai ke kolom yang tidak dideklarasikan akan menghasilkan error waktu kompilasi.

Class dan pewarisan

Awalnya, Anda hanya dapat menentukan class yang merupakan subkelas langsung dari class dasar gcul.Contract. Aturan ketat ini mencegah kompleksitas pewarisan Python penuh, yang dapat menyebabkan bug yang sulit ditemukan dan membuat kode sulit dipahami. Untuk keamanan, upaya mengganti properti atau metode dari class induk akan memunculkan error, sehingga memberikan perlindungan yang jelas terhadap perilaku yang tidak terduga.

Roadmap GCULpy akan menawarkan lebih banyak fleksibilitas sambil mempertahankan prinsip intinya. Roadmap ini mencakup dukungan untuk pewarisan tunggal pada class yang ditentukan pengguna dengan penggantian metode yang dikelola secara eksplisit dengan dekorator @override. Selain itu, fungsi bawaan super() hanya akan didukung dalam bentuk tanpa argumen, untuk memastikan operasi langsung dan dapat diprediksi.

Modul gcul

GCULpy menyediakan modul gcul bawaan dengan jenis dan variabel penting untuk pengembangan kontrak.

kelas gcul.Contract

Class dasar untuk semua kontrak. Anda tidak dapat membuat instance secara langsung; kontrak hanya di-instansiasi melalui transaksi CreateContract. Metode dan properti dari class dasar gcul.Contract tidak dapat diganti di subclass.

  • Contract.is_owner(account: Account) -> bool

    Menampilkan True jika akun yang diberikan adalah pemilik kontrak.

kelas gcul.Account

Jenis bawaan yang merepresentasikan akun pengguna di buku besar. Anda tidak dapat membuat objek gcul.Account secara langsung; lingkungan runtime akan membuatnya untuk Anda dan menyediakannya sebagai argumen fungsi atau metode. Saat Anda meneruskan ID akun sebagai argumen transaksi, runtime akan otomatis memvalidasinya. Jika ID tersebut adalah ID yang valid untuk akun terdaftar, ID tersebut akan dikonversi menjadi objek akun lengkap. Jika tidak, transaksi akan gagal. Hal ini memastikan Anda hanya bekerja dengan akun yang valid.

Definisi class kira-kira setara dengan:

@dataclasses.dataclass(frozen=True)
class Account:
  """A valid account on the ledger."""

  id: str  # The ID of the account as a string.

gcul.sender: gcul.Account

Variabel khusus, yang tersedia dalam metode apa pun, yang menyimpan referensi ke akun yang menandatangani dan mengirimkan transaksi saat ini.

Roadmap Meningkatkan kemampuan developer untuk mengelola dan berinteraksi dengan kontrak dan akun—Anda akan dapat meneruskan referensi ke objek kontrak sebagai argumen, menyimpannya di kolom, mengakses ID uniknya (contract.id: str). Demikian pula, referensi ke objek akun dapat disimpan dan ID-nya dapat diambil.

Operator

Sebagian besar operator yang tersedia di Python didukung di GCULpy dan berfungsi seperti yang diharapkan.

  • Penambahan (+) dan pengurangan (-), termasuk bentuk unary dan biner.
  • Perkalian (*), pembagian floor (//), dan modulo (%).
  • Eksponensiasi (**) untuk eksponen positif.
  • Perbandingan (<, <=, >, >=, ==, !=).
  • Bitwise and (&), or (|), xor (^), left shift (<<), right shift (>>), negasi (~).
  • Operasi Boolean (and, or, not).
  • Identitas Objek Roadmap (is).
  • Batasan Eksponen negatif tidak diizinkan dan akan memunculkan error runtime.
  • Batasan Pembagian sebenarnya (/), karena memiliki jenis nilai yang ditampilkan float, tidak diizinkan dan akan menghasilkan error waktu kompilasi.

Alur kontrol

Sebagian besar pernyataan alur kontrol dari Python berfungsi di GCULpy dengan semantik yang sama:

  • Laporan pass.
  • panggilan fungsi internal (kontrak yang sama, tidak berulang).
  • Laporan assert.
  • Laporan if ... then .. else ....
  • Laporan for VAR in CONTAINER.
  • Panggilan fungsi eksternal Roadmap (ke kontrak lain, non-rekursif).
  • Pernyataan Roadmap break dan continue.
  • Pernyataan Roadmap raise dan try ... except.
  • Pernyataan Roadmap match.
  • Pernyataan Roadmap generators dan yield.
  • Roadmap pengelola konteks dan pernyataan with.

Batasan GCULpy sengaja tidak lengkap Turing untuk mencegah loop tak terbatas, memfasilitasi analisis statis, dan memastikan biaya pemrosesan transaksi yang dapat diprediksi. Berikut cara penegakannya:

  • Tidak ada loop tak terbatas: Iterasi hanya diizinkan menggunakan loop for di atas penampung terbatas; loop while tidak diizinkan. Beberapa pembaruan penampung, misalnya menambahkan atau menghapus elemen ke daftar atau kunci ke kamus, tidak diizinkan saat melakukan iterasi.
  • Tidak ada rekursi: Fungsi tidak dapat memanggil dirinya sendiri, baik secara langsung maupun tidak langsung. Lingkungan runtime melakukan pemeriksaan statis dan runtime untuk mendeteksi dan menolak penggunaan rekursi.
  • Tidak ada alur kontrol asinkron: Penggunaan primitif async tidak diizinkan untuk mempertahankan prinsip desain inti dari prediktabilitas, keamanan, dan eksekusi deterministik. Operasi asinkron menyulitkan untuk memahami alur kontrol program, yang sering kali menyebabkan kerentanan dan kondisi race.

Fungsi bawaan

Rencana Jangka Panjang Berikut adalah rencana jangka panjang kami untuk fungsi bawaan dengan fungsi yang paling mendasar dan umum digunakan untuk membangun dengan percaya diri.

A
abs()
all()
any()

B
bin()
bool()
bytes()

C
chr()

D
dict()
divmod()

E
enumerate()

F
format()
frozenset()

H
hash()
hex()

I
id()
int()

L
len()
list()

M
max()
min()

O
oct()
ord()

P
pow()
property()

R
range()
repr()
reversed()

S
set()
sorted()
staticmethod()
str()
sum()
super()

T
tuple()

Z
zip()

Catatan rilis