GCULpy は、安全で読みやすく、監査可能になるように設計された、Python の厳密な静的型付きサブセットです。この設計では、一般的なスマート コントラクトの脆弱性を防ぎ、コントラクトの動作を常に予測できるようにするために、Python の特定の動的機能が意図的に制限されています。
このページでは、GCULpy 言語仕様のリファレンスを提供します。このリファレンスでは、ユニバーサル台帳ネットワーク上のコントラクトのコアコンセプトとライフサイクルについて説明します。
基本コンセプト
次のコントラクトは、GCULpy でサンプル ERC20 トークンを定義します。
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
GCULpy コントラクトは、gcul.Contract から継承するクラスです。これには、フィールド(状態を保存)とメソッド(フィールドで動作する処理ロジック)が含まれます。
フィールド
GCULpy 契約の状態はフィールドに保存されます。すべてのフィールドは、クラスレベルで静的型を使用して宣言する必要があります。フィールドには次の 2 種類があります。
契約フィールドには、契約自体に保存された単一の値が保持されます。
ERC20Tokenの例では、symbol: strとtotal_supply: intは契約フィールドです。アカウント フィールドには、契約に関与する各ユーザー アカウントの個別の値が保存されます。これらは常に、
gcul.Accountをキーとする辞書(dict)として宣言されます(balance: dict[gcul.Account, int]など)。コントラクトがユーザーのアカウントに書き込むには、ユーザーがコントラクトにストレージ権限を明示的に付与する必要があります。データが保存されると、変更または削除できるのは契約インスタンスのみになります。ユーザーは変更または削除できません。
メソッド
メソッドは、コントラクトの実行可能なロジックを定義します。これらは Python メソッドのように動作し、コントラクトのフィールドを読み取りまたは変更できます。
__init__: コンストラクタは、コントラクトが最初にデプロイされたときに 1 回だけ呼び出されます。これは、コントラクト フィールドの初期状態を設定するために使用されます。コンストラクタで値が割り当てられていないフィールドには、適切なデフォルト値が設定されます。たとえば、intフィールドには0、dictフィールドには空のディクショナリが設定されます。プライベート メソッド: アンダースコアで始まるメソッド(
_internal_logicなど)はプライベートであり、同じコントラクト内の他のメソッドからのみ呼び出すことができます。Universal Ledger インタープリタは、この制約を適用します。パブリック メソッド: アンダースコア(
_)で始まらないメソッドはすべてパブリックです。公開メソッドは、ROLE_CONTRACT_PARTICIPANTを持つユーザーが InvokeContractMethod トランザクションを送信することで呼び出すことができます。
契約ライフサイクル
以降のセクションでは、GCULpy 契約のライフサイクルに関連する一般的なオペレーションについて説明します。
コントラクトをデプロイする
まず、gculpyc コンパイラを使用して GCULpy ソースコードをコンパイルします。ROLE_CONTRACT_CREATOR を持つユーザーは、CreateContract トランザクションを送信して、コンパイルされたバイトコードをユニバーサル台帳ネットワークにデプロイできます。詳細な手順については、プログラマブル コントラクトをデプロイするチュートリアルをご覧ください。
このようなトランザクションは次のようになります。
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" }
}
}
}
}
ネットワークがこのトランザクションを処理すると、次のようになります。
- コンストラクタ(
__init__メソッド)が実行され、新しいコントラクト インスタンスが作成されます。 - トランザクションの送信者がコントラクトの所有者になります。
- コントラクト インスタンスは台帳に永続的に保存され、一意のコントラクト ID が割り当てられます。この ID はトランザクション出力の一部として返されます。
権限を付与する
コントラクトがユーザーに代わってアカウント フィールドにデータを保存するには、ユーザーがコントラクトに保存権限を付与する必要があります。これはセキュリティ上の重要な手順です。ROLE_CONTRACT_PARTICIPANT を持つユーザーは、特定のコントラクト ID の GrantContractPermissions トランザクションを送信できます。
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
app {
[type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
}
ネットワークがこのトランザクションを処理すると、次のようになります。
- コントラクトでアカウント フィールドが定義されていない場合、トランザクションは拒否されます。
- コントラクトでアカウント フィールドが定義されている場合、それらはすべてデフォルト値(
contract.balance[gcul.sender] = 0など)で入力されます。これらの値は、アカウントのデータの一部としてワールド状態に保存され、トランザクション送信者はこの特定のコントラクト インスタンスに参加しているものとして登録されます。
コントラクト メソッドを呼び出す
コントラクトがデプロイされ、必要な権限が付与されると、ユーザーはパブリック メソッドを呼び出してコントラクトを操作できます。ROLE_CONTRACT_PARTICIPANT を持つユーザーは、コントラクト ID、メソッド名、引数値を指定して InvokeContractMethod トランザクションを送信できます。
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 }
}
}
}
}
ネットワークがこのトランザクションを処理すると、次のようになります。
- 指定された
CONTRACT_IDに関連付けられた契約インスタンスが取得されます。 - メソッド
mint(beneficiary=Account("BENEFICIARY_ID"), value=10)が実行されます。受取人のAccountオブジェクトは、ランタイムによってビルドされ、検証されます。メソッドのロジックでは、指定された ID が有効で、台帳上の既存のアカウントを参照していることを安全に想定できます。 - なんらかの理由でメソッドが失敗した場合、トランザクションは失敗し、コントラクトの状態は更新されません。
- メソッドが成功すると、コントラクトの更新された状態がワールド状態に記録されます。
言語仕様
GCULpy は安全性と予測可能性を重視して設計されているため、いくつかの Python 機能が禁止されています。これらの機能は 制限ラベルで示されます。これらの制限は、コントラクト ロジックの読み取り、監査、静的分析を容易にし、予期しない動作や安全でない動作を制限するために導入された、永続的な言語機能です。
他の機能は ロードマップ ラベルで示されています。これらは実装ロードマップにありますが、gculpyc コンパイラではまだサポートされていません。
型
GCULpy は、静的型付けを重視した一般的な変数型を幅広くサポートしています。
コアバリューのタイプ:
int、bool、str、Noneはすでにサポートされています。- ロードマップ
Decimal、bytes、Enumはロードマップに記載されています。 - 制限
floatとcomplexは使用できません。
コンテナタイプ:
dictはすでにサポートされています。- ロードマップ
list、tuple、set、dataclassはロードマップに記載されています。 - 制限事項 コンテナ内の値には、具体的な型を指定する必要があります。たとえば、
dict[str, int]は許可されますが、プレーンなdictやdict[str, Any]は許可されません。 - コンテナのネストがサポートされています(例:
dict[str, list[int]])。
制限事項: コントラクト フィールドとアカウント フィールド、関数パラメータと戻り値の型など、すべての変数は静的に定義され、型指定されている必要があります。型は実行時に変更できず、具体的な型のみがサポートされます。型は値として使用できません。たとえば、変数に格納したり、引数として関数に渡したりすることはできません。宣言されていないフィールドに値を割り当てようとすると、コンパイル時エラーが発生します。
クラスと継承
最初は、基底 gcul.Contract クラスの直接のサブクラスであるクラスのみを定義できます。この厳格なルールにより、完全な Python 継承の複雑さを回避できます。完全な Python 継承は、見つけにくいバグを発生させ、コードの推論を困難にする可能性があります。セキュリティのため、親クラスのプロパティまたはメソッドをオーバーライドしようとするとエラーが発生し、予期しない動作に対する明確な保護手段が提供されます。
ロードマップ GCULpy は、コア原則を維持しながら、より柔軟性を提供します。ロードマップには、@override デコレータで明示的に管理されるメソッドのオーバーライドを使用して、ユーザー定義クラスの単一継承のサポートが含まれています。また、直接的で予測可能なオペレーションを保証するため、super() 組み込みは引数なしの形式でのみサポートされます。
gcul モジュール
GCULpy には、コントラクト開発に必要な型と変数を含む組み込みの gcul モジュールが用意されています。
クラス gcul.Contract
すべてのコントラクトの基本クラス。直接インスタンスを作成することはできません。コントラクトは CreateContract トランザクションでのみインスタンス化されます。基本 gcul.Contract クラスのメソッドとプロパティは、サブクラスでオーバーライドできません。
Contract.is_owner(account: Account) -> bool指定されたアカウントが契約所有者の場合は
Trueを返します。
クラス gcul.Account
台帳上のユーザー アカウントを表す組み込み型。gcul.Account オブジェクトを直接作成することはできません。ランタイム環境がオブジェクトを作成し、関数またはメソッドの引数として提供します。アカウント ID をトランザクション引数として渡すと、ランタイムが自動的に検証します。登録済みアカウントの有効な ID の場合は、完全なアカウント オブジェクトに変換されます。そうでない場合、トランザクションは失敗します。これにより、常に有効なアカウントのみを使用できます。
このクラスの定義は、おおよそ次の定義と同等です。
@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
任意のメソッドで使用できる特別な変数。現在のトランザクションに署名して送信したアカウントへの参照を保持します。
ロードマップ: 契約とアカウントの管理と操作に関するデベロッパーの能力を向上させます。契約オブジェクトへの参照を引数として渡し、フィールドに保存して、一意の ID(contract.id: str)にアクセスできるようになります。同様に、アカウント オブジェクトへの参照を保存して、ID を取得できるようになります。
演算子
Python で使用できるほとんどの演算子は GCULpy でサポートされており、想定どおりに動作します。
- 加算(
+)と減算(-)。単項形式と二項形式を含む。 - 乗算(
*)、切り捨て除算(//)、剰余(%)。 - 正の指数に対するべき乗(
**)。 - 比較(
<、<=、>、>=、==、!=)。 - ビット演算の AND(
&)、OR(|)、XOR(^)、左シフト(<<)、右シフト(>>)、否定(~)。 - ブール演算子(
and、or、not)。 - ロードマップ オブジェクト ID(
is)。 - 制限 負の指数は許可されず、ランタイム エラーが発生します。
- 制限事項
float戻り値の型を持つ真の除算(/)は許可されず、コンパイル時エラーが発生します。
制御フロー
Python のほとんどの制御フロー ステートメントは、同じセマンティクスで GCULpy で動作します。
passステートメント。- 内部関数呼び出し(同じコントラクト、非再帰)。
assertステートメント。if ... then .. else ...ステートメント。for VAR in CONTAINERステートメント。- ロードマップ外部関数呼び出し(他のコントラクトへの呼び出し、非再帰的)。
- ロードマップ
breakステートメントとcontinueステートメント。 - ロードマップ
raiseステートメントとtry ... exceptステートメント。 - ロードマップ
matchステートメント。 - ロードマップ
generatorsステートメントとyieldステートメント。 - ロードマップ コンテキスト マネージャーと
withステートメント。
制限 GCULpy は、無限ループを防ぎ、静的分析を容易にし、予測可能なトランザクション処理コストを確保するために、意図的に チューリング不完全にしています。このポリシーは次のように適用されます。
- 無限ループなし: 繰り返しは、有限コンテナに対する
forループのみが許可されます。whileループは許可されません。コンテナの更新(リストへの要素の追加や削除、辞書へのキーの追加など)は、コンテナの反復処理中は許可されません。 - 再帰なし: 関数は、直接的または間接的に自身を呼び出すことはできません。ランタイム環境は、静的チェックとランタイム チェックの両方を実施して、再帰の使用を検出し、拒否します。
- 非同期制御フローなし: 予測可能性、セキュリティ、決定論的実行というコア設計原則を維持するため、
asyncプリミティブの使用は許可されていません。非同期オペレーションでは、プログラムの制御フローを推論することが難しく、脆弱性や競合状態につながることがよくあります。
組み込みの関数
ロードマップ: 信頼性の高い構築を可能にする、最も基本的な機能とよく使用される機能が組み込まれた関数のロードマップをご紹介します。
|
A
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
リリースノート
- 2026 年 1 月 28 日。Universal Ledger の非公開プレビューの参加者に
gculpycコンパイラの初期バージョンを提供。コンパイラを使用するチュートリアルについては、プログラマブル コントラクトをデプロイするをご覧ください。