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 계약의 상태는 필드에 저장됩니다. 모든 필드는 클래스 수준에서 정적 유형으로 선언해야 합니다. 필드에는 두 가지 종류가 있습니다.
계약 필드는 계약 자체에 저장된 단일 값을 보유합니다.
ERC20Token예시에서symbol: str및total_supply: int는 계약 필드입니다.계정 필드는 계약과 상호작용하는 각 사용자 계정의 별도 값을 저장합니다. 항상
gcul.Account을 키로 사용하는 사전(dict)으로 선언됩니다(예:balance: dict[gcul.Account, int]). 계약이 사용자의 계정에 쓸 수 있으려면 사용자가 계약에 저장소 권한을 명시적으로 부여해야 합니다. 데이터가 저장되면 계약 인스턴스만 데이터를 수정하거나 삭제할 수 있으며 사용자는 수정하거나 삭제할 수 없습니다.
메서드
메서드는 계약의 실행 가능한 로직을 정의합니다. Python 메서드처럼 작동하며 계약의 필드를 읽거나 수정할 수 있습니다.
__init__: 생성자는 계약이 처음 배포될 때 한 번만 호출됩니다. 계약 필드의 초기 상태를 설정하는 데 사용됩니다. 생성자에서 값이 할당되지 않은 필드에는 적절한 기본값이 할당됩니다. 예를 들어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가 할당됩니다.
권한 부여
계약이 사용자를 대신하여 계정 필드에 데이터를 저장하려면 사용자가 먼저 계약에 저장 권한을 부여해야 합니다. 이는 중요한 보안 단계입니다. 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 상속의 복잡성을 방지합니다. 보안을 위해 상위 클래스의 속성이나 메서드를 재정의하려고 하면 오류가 발생하므로 예상치 못한 동작을 방지하는 명확한 보호 장치가 제공됩니다.
로드맵 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일 범용 원장 비공개 미리보기 참여자에게
gculpyc컴파일러의 초기 버전이 제공됩니다. 컴파일러를 사용하는 튜토리얼은 프로그래밍 가능한 계약 배포를 참고하세요.