Compact reference
Overview
Compact는 강타입, 정적 타입의 바운드 스마트 컨트랙트 언어로, Midnight의 3중 구조 스마트 컨트랙트를 TypeScript와 함께 작성하도록 설계되었습니다. 컨트랙트는 다음 구성 요소로 이루어집니다:
- 공개 ledger의 복제 구성 요소,
- 컨트랙트 로직에 따라 복제 구성 요 소가 유효하게 생성되었음을 기밀로 증명하는 영지식 circuit 구성 요소, 그리고
- 임의의 코드를 실행할 수 있는 로컬 오프체인 구성 요소.
각 Compact 프로그램(컨트랙트라고도 함)은 여러 종류의 프로그램 요소를 포함할 수 있습니다:
- 네임스페이스 및 별도 파일 관리를 위한 모듈과 import 형식,
- 프로그램 정의 타입의 선언,
- 컨트랙트가 공개 ledger에 저장하는 데이터의 선언,
- TypeScript 러너가 제공하는 콜백 함수인 witness 선언,
- 스마트 컨트랙트의 운영 핵심인 circuit 정의, 그리고
- 최대 하나의 constructor 정의로, 컨트랙트 생성 시 호출되며 공개 및 비공개 상태를 초기화하는 데 사용할 수 있습니다.
Compact는 TypeScript와 유사합니다: JavaScript와 유사한 구문을 가지며, JavaScript 구문 위에 타입 시스템을 계층화합니다. 그러나 Compact는 여러 중요한 면에서 의도적으로 TypeScript와 다릅니다:
- TypeScript와 달리 Compact는 강타입입니다. 프로그램은 누락된 타입 선언이나 안전하지 않은 캐스트를 통해 정적 타입 시스템을 우회할 수 없습니다. 또한 Compact 컴파일러가 생성하는 JavaScript 코드에는 Compact 외부에서 오는 값의 정적 타입을 강제하는 런타임 검사와, Compact circuit의 선언된 인수 수보다 많거나 적은 인수로의 외부 적용을 방지하는 검사가 포함됩니다.
- Compact는 동적이 아닌 정적 모듈을 통해 네임스페이스 관리를 제공하며, 이러한 모듈은 타입 매개변수뿐만 아니라 크기 매개변수를 포함하는 컴파일 타임 제네릭 매개변수를 통해 매개변수화할 수 있습니다.
- 모든 Compact 프로그램은 유한한 proving circuit 세트 로 컴파일되어야 하므로, 언어는 바운드됩니다: 모든 Compact 타입은 컴파일 타임에 고정되는 크기를 가지며, 루프는 상수 바운드 또는 상수 크기 객체의 크기로 바운드되며, 재귀는 허용되지 않습니다.
- Compact 숫자 값은 프로그램에서 선언된 범위 또는 대상 proving 시스템의 필드 크기에 의해 결정되는 범위의 부호 없는 정수로 제한됩니다.
- Compact는 특정 값을 비공개 데이터를 잠재적으로 포함하는 것으로 구분하며
일반적으로 보호되어야 하고,
disclose()래퍼를 통해 잠재적 비공개 데이터의 공개를 명시적으로 선언하도록 요구합니다. 이에 대한 기본 사항은disclose설명에서 논의되며, 더 자세한 설명과 논의는 별도의 문서 Compact에서의 명시적 공개에 있습니다.
TypeScript와 마찬가지로 Compact는 JavaScript로 컴파일되지만, TypeScript 정의 파일도 생성하므로 효과적으로 TypeScript로도 컴파일됩니다. 단순히 TypeScript를 생성하는 대신 별도의 TypeScript 정의 파일과 JavaScript 구현 파일을 생성하는 이유는 세 가지입니다:
- 컴파일된 Compact 프로그램을 추가 TypeScript 컴파일 단계 없이 사용할 수 있도록,
- 생성된 코드가 TypeScript에서 호출될 때 컴파일 타임 인수 타입 검사를 비활성화하지 않으면서 함수 인수 수를 검사할 수 있도록, 그리고
- 생성된 JavaScript 코드의 요소(예: 변수 바인딩 및 참조)를 소스 Compact 코드의 해당 요소에 올바르게 매핑하는 소스맵 파일을 생성할 수 있도록.
공개 ledger를 건드리므로 온체인 실행을 위한 proof가 필요한 각 circuit에 대해, Compact 컴파일러는 영지식 중간 언어(zkir)로 proving circuit도 생성하며, zkir 컴파일러를 사용하여 각 circuit에 대한 proving 키를 생성합니다.
마지막으로, Compact 컴파일러는 프로그램과 컴파일된 표현에 대한 정보를 포함하는 JSON 형식의 contract info 파일도 생성합니다. 여기에는 버전 번호와 컨트랙트의 내보낸 circuit의 타입 및 특성이 포함됩니다.
이 문서는 각 구문 범주를 개별적으로 설명합니다. 먼저 다양한 컨텍스트에서 사용되는 구성 요소인 식별자, 상수, 타입, 제네릭 매개변수, 패턴을 소개합니다. 그런 다음 Compact 프로그램의 구조, 각 종류의 프로그램 요소, circuit 및 constructor 본문에 나타날 수 있는 문장과 표현식을 설명합니다. 마지막으로 TypeScript 대상에 대해 논의합니다.
컨트랙트 작성하기에서는 Compact 프로그램이 어떻게 생겼는지의 작은 예제를 제공합니다. 또한 Compact 컨트랙트의 기본 구성 요소도 소개합니다. Compact의 전체 문법은 별도로 제공됩니다.
Notation
Compact 프로그램의 구문은 다음 표기 규칙을 사용하는 EBNF 문법 조각으로 제공됩니다:
- 키워드와 구두점은
고정폭글꼴입니다. - 터미널 및 비터미널 이름은 강조 글꼴입니다.
- 대안은 세로 막대(
|)로 표시됩니다. - 선택적 항목은 위첨자 opt로 표시됩니다.
- 반복은 줄임표로 지정됩니다.
X ... X 표기법(여기서 X는 문법 기호)은 X의
0개 이상의 발생을 나타냅니다.
X
,...,X 표기법(여기 서 X는 문법 기호이고,는 리터럴 쉼표)은 쉼표로 구분된 X의 0개 이상의 발생을 나타냅니다. 어느 경우든, 줄임표에 위첨자 1이 표시된 경우, 즉 ...¹인 경우, 표기법은 최소 하나의 X를 포함하는 시퀀스를 나타냅니다. 이러한 시퀀스 뒤에,opt가 오면, 최소 하나의 X가 있는 경우에만 선택적 후행 쉼표가 허용됩니다. 예를 들어, id ... id는 0개 이상의 id를 나타내고, expr,...¹,expr,opt는 하나 이상의 쉼표로 구분된 expr에 선택적으로 추가 쉼표가 뒤따르는 것을 나타냅니다. 쉼표를 포함하는 규칙은 세미콜론에도 동일하게 적용됩니다. 즉,,가;로 대체된 경우에도 적용됩니다.
모든 프로그램은 토큰이라고 알려진 원자적 문자 시퀀스로 구성된
문자로 형성됩니다.
문법 조각에 나타나는 각 키워드와 구두점 기호는 자기 자신을 정확히 나타냅니다.
즉, 동일한 문자 시퀀스로 구성된 토큰을 나타냅니다.
예를 들어, 문법 조각에 키워드 circuit이 나타나면 토큰 circuit만 일치하고,
구두점 기호 :이 나타나면 토큰 :만 일치합니다.
문법 조각에 나타나는 각 터미널 이름은 가능한 토큰의 집합을 나타냅니다. 예를 들어, 터미널 이름 id는 모든 식별자의 집합을 나타냅니다: 문법 조각에 id가 나타나면 모든 식별자와 일치합니다. 문법 조각에 나타나는 터미널 이름이 나타내는 토큰 집합은 아래의 터미널 이름에서 설명됩니다.
문법 조각에 나타나는 각 비터미널 이름은 프로그램의 구조화된 부분을
구성 하는 토큰 시퀀스를 나타냅니다.
예를 들어, 터미널 이름 expr은 3 + x 또는 a ? b : c와 같이
표현식으로 해석될 수 있는 모든 토큰 시퀀스와 일치합니다.
각 비터미널 이름이 나타내는 구조의 집합은 이 레퍼런스 매뉴얼의
다양한 섹션에서 적절한 타이핑 및 평가 규칙과 함께 제공됩니다.
예를 들어, circuit-definition의 구조는
Circuit 정의에서 설명됩니다.
Terminal names
다음 터미널 이름이 문법 조각에 나타납니다.
-
id, module-name, function-name, struct-name, enum-name, contract-name, tvar-name, type-name은 모두 식별자 토큰을 나타냅니다.
-
nat는 자연수 리터럴을 나타냅니다.
-
str과 file은 문자열 리터럴을 나타냅니다.
-
version은 버전 리터럴(pragma에서)을 나타냅니다.
식별자와 문자열 리터럴은 각각 둘 이상의 이름으로 표현되지만, 각각은 가능한 식별자 또는 문자열 리터럴 토큰의 전체 집합을 나타냅니다. 문법 조각은 사용 용도를 제안하기 위해서만 다른 터미널 이름을 사용합니다. 예를 들어 모듈 이름에는 module-name, 타입 변수 이름에는 tvar-name을 사용합니다.
Static and dynamic errors
컴파일러는 잘못된 구 문, 정의되지 않은 식별자 참조, 타입 불일치 등 다양한 종류의 정적 오류를 감지합니다. 하나 이상의 정적 오류를 감지하면 오류에 대한 설명적 오류 메시지를 출력하고 출력을 생성하지 않고 종료합니다.
컴파일러가 생성하는 코드와 사용하는 런타임 라이브러리는 Compact 외부의 코드에서 잘못된 인수 수 또는 타입으로 Compact circuit를 호출하려는 시도나, 부호 없는 값을 해당 값에 비해 너무 작은 부호 없는 타입으로 캐스트하려는 시도 등 다양한 종류의 동적 오류를 감지합니다. 이러한 오류는 생성된 코드가 실행될 때 보고되며 현재 circuit의 평가를 중단시킵니다.
Identifiers, bindings, and scope
식별자는 대부분의 다른 프로그래밍 언어와 마찬가지로 Compact에서 이름을 지정하는 데 사용됩니다.
구문적으로, 식별자는 알파벳 문자, 달러 기호($), 또는 밑줄(_)로 시작하고
하나 이상의 알파벳 문자, 숫자(0 - 9), 달러 기호, 또는 밑줄이 뒤따르는
토큰(원자적 문자 시퀀스)입니다.
일부 식별자는 예약어입니다.
이 중 일부는 Compact 언어 구문의 키워드로 사용됩니다.
예: module, import, circuit, for.
다른 것들, 특히 JavaScript와 TypeScript가 예약한 키워드는
Compact에서 향후 사용을 위해 예약된 것으로 간주됩니다. 예: self, class.
또 다른 것들, 특히 __compact로 시작하는 모든 식별자는
컴파일러 사 용을 위해 예약되어 있습니다.
키워드와 예약어의 포괄적 목록은
키워드와 예약어에서 제공됩니다.
나머지 식별자는 모듈, 타입, 제네릭 매개변수, ledger 필드, 함수(circuit 또는 witness) 이름, 함수 매개변수, 로컬 변수 등 다양한 종류의 엔티티의 특정 인스턴스에 이름을 지정하는 데 사용할 수 있습니다. 이러한 엔티티 중 하나와 연결된, 즉 바인딩된 식별자는 해당 바인딩의 스코프 내 어디서든 참조할 수 있습니다. Compact는 어휘적 스코프를 사용하므로 각 바인딩의 스코프는 프로그램 텍스트의 특정 영역으로 제한됩니다. 바인딩은 동일한 식별자에 대한 바인딩을 포함하는 스코프 내의 일부 프로그램 텍스트 영역에서 섀도잉될 수 있습니다.
같은 스코프에서 식별자가 두 번 이상 바인딩되면 정적 오류입니다. 단, 함수 오버로딩은 동일한 이름의 함수가 다른 시그니처(다른 수 또는 종류의 제네릭 매개변수 및/또는 다른 수 또는 타입의 런타임 매개변수)로 동일한 스코프에서 보이는 것을 허용합니다.
각 바인딩의 스코프는 아래에서 설명하는 것처럼 나타나는 위치에 따라 달라집니다. ("섀도잉되는 경우 제외"라는 단서는 명시적으로 기술되지 않지만 각 경우에 적용됩니다.)
- 컨트랙트의 최외곽 레벨(최상위 레벨이라 함)에서 바인딩된 식별자는 컨트랙트 전체에서 보이지만, 별도의 파일에서 가져온 모듈 내에서는 보이지 않습니다.
- 모듈의 최외곽 레벨에서 바인딩된 식별자는 모듈 전체에서 보입니다. export하지 않으면 모듈 외부에서 보이지 않습니다: export된 바인딩은 모듈에서 import된 곳에서도 보입니 다.
- 모듈, 구조체 선언, 함수 선언의 제네릭 매개변수는 선언 전체에서 보입니다.
- circuit 또는 constructor의 런타임 매개변수는 본문 내에서 보입니다.
- 블록 내의
const바인딩으로 정의된 식별자는 블록 전체에서 보입니다. for루프 헤더의const바인딩으로 정의된 식별자는for루프 전체에서 보입니다.
식별자에 대한 모든 참조는 해당 식별자에 대한 바인딩의 스코프 내에 나타나야 합니다. 이 경우 식별자는 해당 바인딩에 의해 식별자와 연결된 엔티티에 바인딩되었다고 말합니다. 그렇지 않으면 참조는 정적 오류입니다.
예를 들어:
circuit c(): Field {
const answer = 42;
{
const answer = 12;
assert(answer != 42, "shadowing did not work!");
}
return answer; // 42를 반환 (외부 'answer')
}
식별자 c는 c라는 이름의 circuit에 바인딩되며, 이 바인딩은
컨트랙트 전체에서 보이지만, 예제에서 c에 대한 참조는 나타나지 않습니다.
식별자 answer의 첫 번째(외부) 바인딩(값 42)은 내부 블록에서
두 번째(내부) answer 바인딩에 의해 섀도잉되는 경우를 제외하고 c의 본문 전체에서
보이므로, return answer의 answer 참조는 42로 평가됩니다.
answer의 두 번째(내부) 바인딩(값 42)은 내부 블록 전체에서 보이므로,
answer != 42의 answer 참조는 12로 평가됩니다.
스코프를 가지는 것 외에도, 모든 바인딩은 수명을 가집니다. circuit 및 witness 바인딩의 경우, 수명은 사실상 영구적입니다. 즉, 프로그램이 실행될 때마다 바인딩을 사용할 수 있습니다.
ledger 필드 바인딩의 수명은 처음 초기화될 때 시작되며 그 시점부터 사실상 영구적입니다. 필드의 값은 시간이 지남에 따라 변경될 수 있지만, ledger 필드 이름과 컨트랙트의 (복제된) 공개 상태에서 ledger 필드의 위치 사이의 연결은 변경되지 않습니다.
반면에, 모듈 이름, 타입 이름, 제네릭 매개변수의 바인딩은 프로그램이 컴파일될 때만 활성입니다. 즉, 프로그램의 구조와 프로그램이 사용하는 데이터의 형태를 결정하는 데 도움이 되지만 프로그램이 컴파일된 후에는 필요하지 않습니다. (그러나 프로그램의 최상위에서 export된 타입 이름에 대한 TypeScript 바인딩은 생성된 TypeScript 정의 파일에서 계속 존재합니다.)
변수 바인딩, 즉 circuit 매개변수, constructor 매개변수,
const 문과 for 루프로 바인딩된 로컬 변수의 바인딩은 동적 수명을 가집니다.
circuit 또는 constructor의 매개변수 바인딩은 circuit 또는
constructor가 호출될 때 새 수명을 시작하고 circuit 또는
constructor가 종료될 때 끝납니다.
const 문에 의해 설정된 변수 바인딩은 const 문이 평가될 때 새 수명을
시작하고 const 문을 포함하는 블록이 종료될 때 끝납니다.
for 루프 헤더의 const 하위 형식에 의해 설정된 const 바인딩은
루프의 각 반복에서 새 수명을 시작하고 해당 반복이 끝날 때 끝납니다.
변수 바인딩은 여러 수명을 가질 수 있습니다. circuit가 여러 번
호출될 수 있고, 블록이 여러 번 평가될 수 있으며, for 루프가 여러 번
평가되거나 여러 반복을 가 질 수 있기 때문입니다.
그러나 Compact의 변수는 불변입니다: 변수 바인딩의 전체 수명 동안
같은 값을 가집니다.
따라서 단일 수명 동안 값이 변할 수 있어서가 아니라 다른 수명에서
다른 값을 가질 수 있기 때문에 변수라고 합니다.
Generic parameters and arguments
다양한 엔티티, 구체적으로 모듈 선언, 구조체 선언, 타입 별칭 선언, circuit 정의, 그리고 witness 선언은 제네릭 매개변수를 가질 수 있습니다. 즉, 선언 시점이 아닌 사용 시점에서 값이 지정되는 컴파일 타임 타입 및 숫자(자연수) 매개변수입니다. 이를 통해 동일한 제네릭 코드를 다른 특정 타입, 바운드, 크기로 사용할 수 있습니다. 섀도잉되는 경우를 제외하고, 제네릭 매개변수는 엔티티 전체에서 보입니다. 특히, 모듈의 제네릭 매개변수는 모듈 본문에 나타나는 프로그램 요소 내에서 보입니다.
존재하는 경우, 제네릭 매개변수는 제네릭 엔티티(모듈, 구조체, 타입 별칭,
circuit, witness)의 이름 뒤에 꺾쇠 괄호로 묶입니다.
각 매개변수는 타입 이름(예: T) 또는 해시 접두사가 붙은 자연수 이름(예: #N)입니다.
제네릭 자연수 매개변수에는 제네릭 타입 매개변수와 구별하기 위해 # 접두사가 붙습니다.
| gparams | ⟶ | < generic-param , ⋯ , generic-param ,opt > |
| generic-param | ⟶ | # tvar-name |
| | | tvar-name |
제네릭 엔티티는 사용 시점에서 특수화되어 컴파일 타임에 비제네릭 엔티티를 생성하려면 제네릭 인수를 제공해야 합니다. 특수화하지 않고 제네릭 엔티티를 사용하려는 시도는 정적 오류입니다. 제네릭 인수도 꺾쇠 괄호로 묶입니다. 각 제네릭 인수는 타입, 자연수 리터럴, 또는 제네릭 매개변수의 타입이나 숫자 값이어야 합니다.
타입 구문은 제네릭 매개변수에 대한 참조를 포함한 타입 참조를 허용하므로, 모든 제네릭 인수는 특수화 시점에서 보이는 제네릭 타입 또는 자연수 매개변수의 값을 전달할 수 있습니다.
제네릭 자연수 매개변수를 제네릭 타입 매개변수와 구별하기 위해 사용하는 #는
특수화 시점에서 사용할 필요가 없으며 사용해서는 안 됩니다.
그러나 제네릭 매개변수에 대해 제공된 제네릭 인수가 숫자 값이 예상될 때
숫자가 아니거나 타입이 예상될 때 타입이 아니면 정적 오류입니다.
아래 예제는 모듈 레벨과 circuit 레벨의 두 수준의 제네릭 매개변수화 사용을 보여줍니다.
module M<T, #N> {
export circuit foo<A>(x: T, v: Vector<N, A>): Vector<N, [A, T]> {
return map((y) => [y, x], v);
}
}
import M<Boolean, 3>;
export circuit bar1(): Vector<3, [Uint<8>, Boolean]> {
return foo<Uint<8>>(true, [101, 103, 107]);
}
export circuit bar2(): Vector<3, [Boolean, Boolean]> {
return foo<Boolean>(false, [true, false, true]);
}
circuit foo의 본문은 모듈의 타입 매개변수 T와 N 그리고
circuit 자체의 매개변수 A에 대해 제네릭입니다.
모듈은 import 시점에서 특수화되고, circuit는 호출 시점에서
(bar1과 bar2 모두에서) 특수화됩니다.
Compact types
Compact는 정적 타입입니다: Compact 프로그램의 모든 표현식은 정적 타입을
가져야 합니다.
이름이 지정된 circuit와 witness의 경우, 매개변수와 반환 타입을
명시적으로 선언해야 합니다.
익명 circuit 표현식의 경우, 매개변수와 반환 타입을 선언할 필요가 없지만
선언할 수 있습니다.
const 바인딩의 타입도 선언하거나 생략할 수 있습니다.
언어는 강타입입니다: 컴파일러는 타입 검사를 통과하지 못하는 프로그램을 거부합니다. 예를 들어, 매개변수 타입 주석이 있는 circuit이나 witness가 해당 매개변수에 대해 잘못된 타입의 인수로 호출되는 프로그램을 거부하고, 반환 타입 주석이 있는 circuit이 잘못된 타입의 값을 반환하는 프로그램을 거부합니다. 선택적 타입 주석이 생략되면 컴파일러는 타입을 추론하려고 시도하며, 추론할 수 없으면 프로그램을 거부합니다.
타입은 내장 원시 타입, ledger 상태 타입, 프로그램 정의 타입,
스코프 내의 제네릭 타입 매개변수 참조로 구성됩니다.
이 문서에서 다른 한정자 없이 "타입"이라는 용어가 나타나면,
원시 타입, ledger 상태 타입, 프로그램 정의 타입, 또는 스코프 내의
제네릭 타입 매개변수를 의미합니다.
현재 ledger 상태 타입의 사용은 타입 T의 기본값을 얻기 위한
default<T>의 결과 타이핑으로 제한되며, 상수 바인딩만
ledger 상태 타입을 가질 수 있습니다.
제네릭 타입은 유효한 타입이 아니므로 예를 들어 매개변수나 반환 값의 타입으로 사용할 수 없습니다. 그렇게 하려는 시도는 정적 오류입니다. 다른 제네릭 엔티티와 마찬가지로 사용 시점에서 특수화해야 합니다.
Primitive types
다음은 Compact의 원시 타입입니다:
| type | ⟶ | tref |
| | | Boolean | |
| | | Field | |
| | | Uint < tsize > | |
| | | Uint < tsize .. tsize > | |
| | | Bytes < tsize > | |
| | | Opaque < str > | |
| | | Vector < tsize , type > | |
| | | [ type , ⋯ , type ,opt ] | |
| tref | ⟶ | id gargsopt |
| tsize | ⟶ | nat |
| | | id |
-
Boolean은 불리언 값의 타입입니다.Boolean타입의 값은 두 가지뿐입니다.true와false표현식의 값입니다. -
Uint<m..n>에서 m은 리터럴0또는0에 바인딩된 제네릭 자연수 매개변수이고, n은 0이 아닌 자연수 리터럴 또는 0이 아닌 자연수에 바인딩된 제네릭 자연수 매개변수인 경우,0(포함)과n(미포함) 사이의 바운드된 부호 없는 정수 값의 타입입니다. (현재 하한은0이어야 하지만, 이 제한은 향후 해제될 수 있습니다.) 다른 상한을 가진Uint타입은 다른 타입이지만, 하한이 더 낮은 것은 다른 타입의 서브타입입니다. 컴파일러와 런타임 시스템은 지원되는 부호 없는 정수 값의 범위에 제한을 부과할 수 있습니다. 그렇다면Uint타입이 이 제한을 초과하는 값을 포함할 때마다 정적 오류입니다. 현재 제한(있는 경우)은 구현별 제한에서 제공됩니다. -
Uint<n>에서 n은 0이 아닌 자연수 리터럴 또는 0이 아닌 자연수에 바인딩된 제네릭 자연수 매개변수인 경우, 최대 n비트의 이진 표현을 사용하는 크기 지정 부호 없는 정수 값의 타입입니다. 이는 m이 2n인Uint<0..m>과 동일한 타입입니다. 크기 지정 정수 타입은 프로그래머의 편의를 위한 것입니다. 예를 들어Uint<32>는 동등한Uint<0..4294967296>보다 더 명확하고 오류가 적을 수 있습니다. 크기 지정 정수 타입을 사용하는 모든 Compact 프로그램은 바운드된 정수 타입만 사용하는 것으로 다시 작성할 수 있지만, 그 반대는 성립하지 않습니다. -
Field는 ZK proving 시스템의 네이티브 소수 필드 의 차수까지의 부호 없는 정수 집합을 나타냅니다. 현재 최대 필드 값은 구현별 제한에서 제공됩니다. -
[T1, ..., Tn]에서 T1, ..., *Tn*이 0개 이상의 쉼표로 구분된 타입인 경우, 요소 타입 T1, ..., *Tn*을 가진 튜플 값의 타입입니다. 튜플은 이질적입니다: 모든 요소 타입이 다른 것과 다를 수 있습니다. 튜플 타입의 길이는 요소 타입의 수 n입니다. 길이가 다른 두 튜플 타입은 다른 타입입니다. 하나의 요소 타입이 다른 것의 해당 요소 타입과 다른 두 튜플 타입도 다른 타입이지만, 하나의 튜플 타입이 다른 것의 서브타입일 수 있습니다. -
Vector<n, T>에서 n은 자연수 리터럴 또는 제네릭 자연수 매개변수이고 T는 타입인 경우, 타입 T의 n개 발생을 가진 튜플 타입[T, ..., T]의 축약 표기입니다. 벡터 타입과 해당 튜플 타입은 정확히 같은 타입을 쓰는 두 가지 다른 방법입니다. 달리 명시되지 않는 한, 벡터 타입에 대한 타입 규칙은 해당 튜플 타입에 대한 규칙에서 파생됩니다. -
Bytes<n>에서 n은 자연수 리터럴 또는 제네릭 자연수 매개변수인 경우, 길이 n의 바이트 벡터 타입입니다. 길이가 다른Bytes타입은 다른 타입입니다.Bytes타입은 Compact 표준 라이브러리에서 해싱에 사용됩니다. Compact의 문자열 리터럴도Bytes타입을 가지며, n은 문자열의 UTF-8 인코딩의 바이트 수입니다. -
Opaque<s>에서 s가 문자열 리터럴인 경우, 태그 s를 가진 불투명 값의 타입입니다. 다른 태그를 가진Opaque타입은 다른 타입입니다. 불투명 값은 witness에서 조작할 수 있지만 circuit에서는 불투명합니다. circuit에서는 해시로 표현됩니다. 현재 허용되는 태그는"string"과"Uint8Array"뿐입니다.
Program-defined types
프로그램은 세 종류의 새로운 타입을 정의할 수 있습니다: 구조체, 열거형, 컨트랙트. 또한 기존 타입에 대한 구조적 및 명목적 별칭도 정의할 수 있습니다.
Structure types
구조체 타입은 다음 형식의 struct 선언을 통해 정의됩니다:
| struct-declaration | ⟶ | exportopt struct struct-name gparamsopt { typed-id ; ⋯ ; typed-id ;opt } ;opt |
| | | exportopt struct struct-name gparamsopt { typed-id , ⋯ , typed-id ,opt } ;opt | |
| typed-id | ⟶ | id : type |
구조체 선언은 쉼표 또는 세미콜론으로 구분되어야 하는 명명된 필드의 시퀀스를 가집니다. 쉼표와 세미콜론 구분자는 단일 구조체 선언 내에서 혼합할 수 없습니다. 후행 구분자는 허용되지만 필수는 아닙니다.
각 구조체 필드에는 타입 주석이 있어야 합니다. 다음은 몇 가지 예입니다:
struct Thing {
triple: Vector<3, Field>,
flag: Boolean,
}
struct NumberAnd<T> {
num: Uint<32>;
item: T
}
첫 번째 선언은 두 필드를 가진 Thing이라는 구조체 타입을 도입합니다:
triple(세 개의 Field 요소를 가진 벡터)과 flag(불리언).
두 번째는 제네릭 매개변수 T와 두 필드를 가진 제네릭 구조체 타입
NumberAnd를 도입합니다: num(32비트 부호 없는 정수)과 item(타입 T의 값).
제네릭 구조체 타입은 고정 타입이 아니므로 결국 제네릭 인수를 제공하여
특수화해야 합니다. 예: NumberAnd<Uint<8>>.
제네릭 구조체 타입이 특수화될 때, 완전히 특수화되어야 합니다:
제공된 제네릭 인수의 수는 선언된 제네릭 매개변수의 수와 일치해야 합니다.
제네릭 구조체 타입을 특수화하는 효과는 제네릭 매개변수가 제네릭 인수
값으로 대체된 것과 같은 타입을 생성하는 것입니다.
예를 들어, NumberAnd<Uint<8>>는 NumberAnd가 다음과 같이 정의된 경우의
NumberAnd와 동일합니다:
struct NumberAnd {
num: Uint<32>;
item: Uint<8>
}
제네릭 구조체 타입이 프로그램의 다른 부분에서 다른 제네릭 인수를 통해 특수화되어 다른 특수화된 구조체 타입을 생성하는 것은 가능하고 일반적입니다.
구조체 타이핑은 항상 명목적입니다: 두 타입은 같은 이름과 같은 필드를
가질 때만 동등합니다.
같은 필드를 가지더라도 다른 이름을 가지면 구별됩니다.
더 정확히: 각 구조체 타입은 같은 이름, 같은 요소 이름(같은 순서), 같은 요소
타입(같은 순서)을 가진 다른 구조체 타입과 동일합니다.
다른 모든 타입과 구별됩니다.
이는 예를 들어 다음 프로그램이 올바르게 타입이 지정됨을 의미합니다:
module M {
struct NumberAnd {
num: Uint<32>;
item: Uint<8>
}
export circuit bar(x: NumberAnd): NumberAnd {
return x;
}
}
import M;
struct NumberAnd<T> {
num: Uint<32>;
item: T
}
export circuit foo(x: NumberAnd<Uint<8>>): NumberAnd<Uint<8>> {
return bar(x);
}
구조체 타입은 재귀적이어서는 안 됩니다. 즉, 직접적으로든 간접적으로든 구조체와 같은 타입의 요소를 포함할 수 없습니다. 재귀적 구조체 타입을 정의하려는 시도는 정적 오류입니다. 예를 들어, 다음 선언 쌍을 사용하는 것은 정적 오류입니다:
struct Even {
predecessor: Odd
}
struct Odd {
predecessor: Even
}
export circuit doesntWork(s: Even): Odd {
return s.predecessor;
}
구조체 타입의 값은 구조체 생성 표현식으로 생성되고 구조체 필드 접근 표현식을 통해 접근됩니다.
Enumeration types
열거형 타입은 다음 형식의 enum 선언을 통해 정의됩니다:
열거형 선언은 쉼표로 구분된 비어 있지 않은 명명된 요소의 시퀀스를 가집니다. 후행 쉼표는 허용되지만 필수는 아닙니다.
열거형 선언은 아래 예의 Arrow와 같이 명명된 열거형 타입을 도입합니다:
enum Arrow { up, down, left, right };
이 선언의 스코프 내에서 Arrow 타입의 값은 네 가지 값 중 하나를 가질 수 있으며,
Arrow.up, Arrow.down, Arrow.left, Arrow.right를 통해 선택됩니다.
두 열거형 타입은 같은 이름과 같은 요소 이름(같은 순서)을 가지면 동일하고 그렇지 않으면 구별됩니다.
Contract types
Compact 1.0은 컨트랙트 선언과 이를 지원하는 크로스-컨트랙트 호출을
완전히 구현하지 않지만, 컨트랙트를 선언하는 데 사용되는 키워드 contract는
이 용도로 예약되어 있습니다.
Type aliases
타입 별칭은 다음 형식의 type 선언을 통해 생성할 수 있습니다:
type-name에 대한 type의 타입 별칭 선언의 스코프 내에서, type-name은
그 자체로 타입입니다.
타입 별칭은 선택적 new 수정자의 존재 여부에 따라 구조적 또는 명목적입니다:
- 선택적
new수정자 없이 선언된 type에 대한 타입 별칭 type-name은 구조적 타입 별칭입니다. 즉, type-name은 type과 동일한 타입이며 완전히 상호 교환 가능합니다. - 선택적
new수정자와 함께 선언된 type에 대한 타입 별칭 type-name은 명목적 타입 별칭입니다. 즉, type-name은 type과 호환되지만 type (또는 다른 타입)의 서브타입도 슈퍼타입도 아닌 고유 타입입니다.
타입 type에 대한 모든 명목적 타입 별칭 type-name은 다음과 같은 의미에서 type과 호환됩니다:
- type-name 타입의 값은 type 타입의 값과 동일한 표현을 가집니다
- type-name 타입의 값은 type 타입의 값을 요구하는 원시 연산에서 사용할 수 있습니다
- type-name 타입의 값은 type으로 명시적으로 캐스트할 수 있으며
- type 타입의 값은 type-name 타입으로 명시적으로 캐스트할 수 있습니다.
예를 들어, 다음의 스코프 내에서
new type V3U16 = Vector<3, Uint<16>>
V3U16 타입의 값은 Vector<3, Uint<16>> 타입의 벡터처럼 참조하거나
슬라이스할 수 있지만, 명시적 캐스트 없이는 Vector<3, Uint<16>> 타입의 값을
기대하는 함수에 전달할 수 없습니다.
산술 연산(예: +)의 한 피연산자가 명목적 타입 별칭 type-name의 값을 받으면,
다른 피연산자도 type-name 타입이어야 하며, 연산 수행 결과는 type-name
타입으로 캐스트됩니다.
결과를 type-name 타입으로 표현할 수 없으면 동적 오류가 발생할 수 있습니다.
명목적 타입 별칭 type-name의 값은 type의 값을 포함하여 다른 타입의
값과 < 또는 == 등을 사용하여 직접 비교할 수 없습니다.
이러한 비교는 피연산자 중 하나를 다른 것의 타입으로 캐스트해야 합니다.
구조적 및 명목적 타입 별칭 모두 제네릭 매개변수를 취할 수 있습니다. 예:
type V3<T> = Vector<3, T>;
그리고
new type VField<#N> = Vector<N, Field>;
제네릭 명목적 타입이 특수화되면, 특수화된 타입은 명목적 타입입니다.
Subtyping and least upper bounds
일부 Compact 타입은 서브타이핑을 통해 다른 타입과 관련됩니다.
비공식적으로, 타입 T가 타입 S의 서브타입이면(동등하게, S가
타입 T의 슈퍼타입이면), T 타입의 모든 값은 S 타입의 값이기도 합니다.
즉, 명시적 캐스트 없이 S 타입의 값이 예상되는 곳에 T 타입의 값을
사용할 수 있습니다.
예를 들어, circuit 또는 witness는 해당 매개변수 타입 주석의
서브타입인 타입의 인수 표현식으로 호출할 수 있으며,
타입 주석이 있는 const 바인딩 문은 타입 주석의 서브타입인 타입의
표현식으로 값을 줄 수 있습니다.
서브타이핑은 다음 규칙에 의해서만 정의됩니다:
- 모든 타입 T는 자기 자신의 서 브타입입니다(서브타이핑은 반사적)
- n이 m보다 작거나 같으면
Uint<0..n>은Uint<0..m>의 서브타입입니다 - n-1이 최대 필드 값보다 작거나 같으면
Uint<0..n>은Field의 서브타입입니다. (최대 필드 값보다 n-1이 큰 경우Field가Uint<0..n>의 서브타입인지는 현재 지정되지 않았습니다.) - 튜플 타입
[T1, ..., Tn]는 같은 길이이고 각 타입 *Ti*가 해당 타입 *Si*의 서브타입이면 튜플 타입[S1, ..., Sn]의 서브타입입니다.
타입 집합 {T1, ..., Tn}의 (서브타이핑에 대한) 최소 상한은 다음과 같은 타입 S입니다:
- S는 상한: 1..n 범위의 모든 i에 대해 *Ti*는 S의 서브타입이고,
- S는 최소 상한: 타입 집합 {T1, ..., Tn}의 모든 상한 R에 대해 S는 R의 서브타입입니다.
일부 타입(예: Boolean과 Field)은 관련이 없으므로 모든 타입 집합에
최소 상한이 존재하는 것은 아닙니다.
튜플 및 벡터 타입:
모든 벡터 타입은 일부 튜플 타입과 동일합니다.
구체적으로, 원시 타입에 대한 이전 섹션에서 언급했듯이,
벡터 타입 Vector<n, T>는 T가 n번 발생하는 튜플 타입
[T, ..., T]와 동일합니다.
따라서 위의 튜플 타입에 대한 서브타이핑 규칙은 벡터 타입에도 적용됩니다:
모든 벡터 타입은 동일한 튜플 타입의 서브타입이며 일부 다른 튜플 및 벡터 타입의
서브타입일 수 있습니다.
일반적으로, 벡터 타입 Vector<n, T>는 T가 각 타입
S1, ..., *Sn*의 서브타입이면
튜플 타입 [S1, ..., Sn]의 서브타입입니다.
이는 예를 들어 튜플이 예상되는 circuit에 벡터를 종종 전달할 수 있음을 의미합니다.
반면에, 튜플 타입이 항상 동일한 벡터 타입을 가지는 것은 아닙니다.
예를 들어 [Boolean, Field]이나 [Uint<8>, Uint<16>]은 어떤 벡터 타입과도
동일하지 않습니다.
그러나 가능하게 다른 타입 T1, ..., *Tn*을 가진
튜플 타입 [T1, ..., Tn]이
타입 집합 {T1, ..., Tn}의 최소 상한 S가
존재하면 "벡터 타입을 가진다"고 말합니다.
이 경우, 튜플 타입은 벡터 타입 Vector<n, S>를 가집니다.
튜플에 대한 일부 연산(매핑 및 폴딩 등)은 튜플 타입이 벡터 타입을
가질 것을 요구합니다.
튜플 타입이 벡터 타입을 가지면, 튜플 타입은 벡터 타입의 서브타입이지만
벡터 타입과 동일하지 않을 수 있습니다.
예를 들어, [Uint<16>, Uint<16>]은 벡터 타입 Vector<2, Uint<16>>을 가지고
두 타입은 동일하지만, [Uint<8>, Uint<16>]도 벡터 타입 Vector<2, Uint<16>>을
가지지만 타입은 동 일하지 않습니다.
Patterns and destructuring
circuit 또는 constructor의 매개변수와 const 바인딩의 대상은
패턴을 통해 지정됩니다:
| pattern | ⟶ | id |
| | | [ patternopt , ⋯ , patternopt ,opt ] | |
| | | { pattern-struct-elt , ⋯ , pattern-struct-elt ,opt } | |
| pattern-struct-elt | ⟶ | id |
| | | id : pattern |
가장 간단한 형태에서 패턴은 단순히 식별자입니다.
예를 들어, 아래 코드에서 sumTuple의 매개변수는 식별자 x이고
두 const 바인딩의 대상은 식별자 a와 b입니다.
circuit sumTuple(x: [Field, Field]): Field {
const a = x[0], b = x[1];
return a + b;
}
매개변수 타입이 튜플, 벡터, 구조체인 경우, 사용 시점마다 추출하는 대신 바인딩 시점에서 튜플이나 구조체의 개별 부분에 이름을 지정하기 위해 패턴의 구조 분해 형식 중 하나를 사용하는 것이 종종 편리합니다. 예를 들어, 위의 코드를 다음으로 대체할 수 있습니다:
circuit sumTuple(x: [Field, Field]): Field {
const [a, b] = x;
return a + b;
}
또는 더 간단히:
circuit sumTuple([a, b]: [Field, Field]): Field {
return a + b;
}
다음은 튜플 대신 구조체를 구조 분해하는 유사한 예입니다:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumStruct({x, y}: S): Uint<64> {
return x + y;
}
튜플 패턴의 요소는 필연적으로 순서대로 주어지지만,
구조체 패턴의 요소는 선언에서 필드의 순서와 일치할 필요가 없습니다.
예를 들어, 아래의 sumStruct 정의는 위의 것과 동일합니다.
패턴 요소의 순서가 바뀌었음에도 불구하고:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumStruct({y, x}: S): Uint<64> {
return x + y;
}
기본적으로 패턴에 의해 바인딩되는 이름은 구조체 요소의 이름과 동일합니다. 이것이 편리하지 않을 때, 구조체 요소에 대해 다른 이름을 선택할 수 있습니다:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumStruct({x: a, y}: S): Uint<64> {
return a + y;
}
x: a는 타입 주석이 있는 식별자처럼 보이지만, 이 컨텍스트에서는
x 대신 a가 x 필드의 값에 바인딩됨을 나타냅니다.
패턴은 임의로 중첩될 수 있습 니다. 예:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumTupleStruct([{x: a1, y: b1}, {x: a2, y: b2}]: [S, S]): Uint<64> {
return a1 + b1 + a2 + b2;
}
튜플이나 구조체의 특정 부분에 이름을 지정하지 않는 것이 허용되며 때때로 유용합니다.
struct S { x: Uint<16>, y: Uint<32> }
circuit sumSomeYs([{y: b1}, , {y: b3}]: [S, S, S]): Uint<64> {
return b1 + b3;
}
여기서 입력은 세 개의 요소를 가진 튜플이지만, 패턴은 첫 번째와 세 번째
사이에 두 개의 쉼표를 넣어 두 번째를 건너뜁니다.
마찬가지로, 튜플의 각 요소는 x와 y 필드가 모두 있는 구조체이지만,
패턴은 단순히 언급하지 않음으로써 x 필드를 무시합니다.
패턴이 구조 분해될 값의 선언된 또는 추론된 타입과 다른 형태를 의미하면 정적 오류입니다. 예를 들어:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumStruct({x, y}: [Uint<16>, Uint<32>]): Uint<64> {
return x + y;
}
는 튜플을 구조체로 취급하려고 하므로 실패하며:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumSomeYs([{y: b1}, , , {y: b3}]: [S, S, S]): Uint<64> {
return b1 + b3;
}
는 입력 튜플이 실제로 세 개인데 네 개의 요소(건너뛴 두 요소 포함)를 가지는 것으로 의미하므로 실패하며:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumSomeYs([{y: b1}, , {z: b3}]: [S, S, S]): Uint<64> {
return b1 + b3;
}
는 구조체 중 하나에서 존재하지 않는 z 필드에 이름을 지정하려고 하므로 실패합니다.
패턴의 후행 쉼표는 입력의 구조에 대해 아무것도 의미하지 않으며 무시됩니다:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumSomeYs([{y: b1,}, , {y: b3,},]: [S, S, S]): Uint<64> {
return b1 + b3;
}
Programs
Compact 프로그램은 0개 이상의 프로그램 요소의 시퀀스입니다.
간략히:
- pragma 형식은 프로그램이 요구하는 컴파일러 및/또는 언어 버전을 선언할 수 있게 합니다.
- 모듈 정의는 자체 중첩된 스코프에 프로그램 요소의 시퀀스를 포함하는 Compact 모듈을 정의합니다.
- export 형식은 모듈 또는 프로그램 자체에서 바인딩을 export합니다.
- import 형식은 Compact 모듈에서 바인딩을 import합니다.
- include 형식은 다른 파일에서 프로그램 요소를 포함할 수 있게 합니다.
- 구조체 정의는 구조체 타입을 정의합니다.
- 열거형 정의는 열거형 타입을 정의합니다.
- 컨트랙트 선언은 컨트랙트 타입을 선언합니다.
- 타입 별칭 정의는 타입 별칭을 정의하며, 가능하면 고유 타입을 생성합니다.
- witness 선언은 DApp이 제공하는 콜백 함수인 witness를 선언합니다.
- ledger 선언은 컨트랙트의 공개 상태의 한 필드를 선언합니다.
- constructor 정의는 컨트랙트의 constructor를 정의합니다(있는 경우).
- circuit 정의는 circuit를 정의합니다.
프로그램이나 모듈에서 프로그램 요소의 순서는 중요하지 않습니다. 단, 모듈은 해당 모듈의 import 이전에 정의되어야 하며, import 형식에서 제네릭 매개변수로 사용되는 프로그램 정의 타입은 import 형식 이전에 정의되어야 합니다.
구조체, 열거형, 컨트랙트, 타입 별칭 선언에 대한 자세한 설명은 위의 Compact 타입에 있습니다. 나머지 프로그램 요소는 다음 섹션에서 자세히 설명합니다.
Pragma
pragma는 다음 형식을 취하며 컴파일러 버전(id = compiler_version) 또는
언어 버전(id = language_version)에 대한 제약 조건을 선언합니다.
| pragma-form | ⟶ | pragma id version-expr ; |
| version-expr | ⟶ | version-expr || version-expr0 |
| | | version-expr0 | |
| version-expr0 | ⟶ | version-expr0 && version-term |
| | | version-term | |
| version-term | ⟶ | version-atom |
| | | ! version-atom | |
| | | < version-atom | |
| | | <= version-atom | |
| | | >= version-atom | |
| | | > version-atom | |
| | | ( version-expr ) | |
| version-atom | ⟶ | nat |
| | | version |
version은 점으로 구분된 두 개 또는 세 개의 자연수 쌍이므로,
nat과 함께 version은 단일 자연수, .로 구분된 두 자연수,
또는 .로 구분된 세 자연수일 수 있습니다. 예: 1, 1.2, 1.2.7.
예를 들어, 프로그램에 다음 pragma 형식이 포함되어 있으면:
pragma compiler_version >= 1.0.0 && !1.0.5;
1.0.0부터의 모든 컴파일러 버전은 1.0.5를 제외하고 pragma를 수락하고 프로그램을 컴파일하며, 다른 모든 버전은 적절한 메시지를 출력하고 프로그램 컴파일을 중단합니다.
Modules, exports, and imports
Compact에서 모듈은 네임스페이스 관리와 프로그램을 여러 파일로 분할하는 데 사용됩니다. 모듈은 모듈 정의를 통해 생성되는 명명된 프로그램 요소 모음으로, 다음 형식을 취합니다:
| module-definition | ⟶ | exportopt module module-name gparamsopt { program-element ⋯ program-element } |
모듈 정의는 module-name에서 모듈까지의 바인딩을 모듈 정의를 포함하는 프로그램 이나 모듈에서 보이게 만듭니다. 모듈 내의 프로그램 요소에 의해 설정된 바인딩은 보이게 만들어지지 않습니다. 적어도 모듈이 import될 때까지는 그렇습니다.
모듈은 제네릭 매개변수를 가질 수 있으며, 이 경우 제네릭 모듈이며 import 시점에서 제네릭 인수로 특수화되어야 합니다.
Export
기본적으로 모듈 본문 내의 프로그램 요소에 의해 생성된 식별자 바인딩은
모듈 내에서만 보입니다. 즉, 모듈에서 export되지 않습니다.
모듈의 최상위에서 정의되거나 import된 식별자는 두 가지 방법 중 하나로
모듈에서 export할 수 있습니다: (1) 정의 앞에 export 키워드를 붙이거나,
별도의 export 선언에서 식별자를 나열하여:
예를 들어, 다음 모듈은 G와 S를 export하지만 F는 export하지 않습니다.
module M {
export { G };
export struct S { x: Uint<16>, y: Boolean }
circuit F(s: S): Boolean {
return s.y;
}
circuit G(s: S): Uint<16> {
return F(s) ? s.x : 0;
}
}
모듈에서 바인딩을 export하는 것은 모듈이 import되지 않으면 효과가 없습니다.
Import
모듈은 다른 모듈이나 프로그램 최상위로 import하여 export된 바인딩의 일부 또는 전부를 보이게 만들 수 있으며, 접두사를 가질 수도 있습니다.
| import-form | ⟶ | import import-selectionopt import-name gargsopt import-prefixopt ; |
| import-selection | ⟶ | { import-element , ⋯ , import-element ,opt } from |
| import-element | ⟶ | id |
| | | id as id | |
| import-name | ⟶ | id |
| | | file | |
| import-prefix | ⟶ | prefix id |
예를 들어:
module Runner {
export circuit start(): [] {}
export circuit stop(): [] {}
}
module UseRunner1 {
import Runner;
// start와 stop이 이제 스코프에 있음
}
module UseRunner2 {
import { start } from Runner;
// start가 이제 스코프에 있지만, stop은 아님
}
module UseRunner3 {
import Runner prefix Runner$;
// Runner$start와 Runner$stop이 이제 스코프에 있지만, stop이나 run은 아님
}
그리고
module Identity<T> {
export { id }
circuit id(x: T): T {
return x;
}
}
import Identity<Field>;
// id가 이제 스코프에 있으며, Field 타입으로 특수화됨
import-name이 식별자이고 import-name에 대한 import가 import-name의 보이는 정의 전에
나타나면, 모듈이 파일 시스템에 있는 것으로 가정하고 거기서 직접 import됩니다.
import-name이 식별자 module-name이면, 모듈 module-name에 대한 정의가
import하는 파일과 같은 디렉터리의 module-name.compact 파일 또는
Compact 경로의 디렉터리 중 하나에 포함되어야 합니다.
import-name이 문자열 "{prefix/}module-name"인 경우 {prefix/}이
비어 있거나 디렉터리 구분자로 끝나는 경로명이면, module-name이라는 이름의 모듈에 대한
정의가 다음 중 하나의 module-name.compact 파일에 포함되어야 합니다:
- (a)
{prefix/}module-name.compact가 절대 경로명이면 정확히{prefix/}module-name.compact에, 그렇지 않으면 - (b) import하는 파일의 디렉터리 또는 Compact 경로의 디렉터리 중 하나에 대해
{prefix/}module-name.compact에. 검색 순서와 Compact 경로 설정 메커니즘에 대한 자세한 내용은 컴파일러 사용법을 참고하세요.
이러한 경우 중 어느 것이든, module-name.compact를 찾을 수 없거나,
module-name이라는 이름의 모듈에 대한 정의가 포함되어 있지 않거나,
주석과 공백 외에 다른 것이 포함되어 있으면 정적 오류입니다.
몇 가지 예가 이어집니다.
예제 1: 아래 M.compact 파일은 단일 모듈 정의를 포함합니다:
module M {
export { F };
export struct S { x: Uint<16>, y: Boolean }
circuit F(x: S): Boolean {
return x.y;
}
}
그러면 test1.compact는 M.compact에서 M을 import합니다:
import M;
export { F };
반면 test2.compact는 자체 M 정의를 사용합니다:
module M {
export { G };
export struct S { x: Uint<16>, y: Boolean }
circuit G(x: S): Boolean {
return x.y;
}
}
import M;
export { G };
경로명으로 import하면 같은 이름의 여러 모듈을 같은 스코프로 import할 수 있습니다. 예를 들어:
M.compact 파일은 이전과 같이 단일 모듈 정의를 포함합니다:
module M {
export { F };
export struct S { x: Uint<16>, y: Boolean }
circuit F(x: S): Boolean {
return x.y;
}
}
그리고 A/M.compact는 다른 모듈 정의를 포함합니다:
module M {
export { F };
export struct S { x: Uint<16>, y: Boolean }
circuit F(x: S): Boolean {
return x.y;
}
}
그러면 test.compact 프로그램은 M을 정의하고 M, "M", "A/M" 세 가지를
모두 import할 수 있습니다:
module M {
export { F };
export struct S { x: Uint<16>, y: Boolean }
circuit F(x: S): Boolean {
return x.y;
}
}
import M prefix M1$;
import "M" prefix M2$;
import "A/M" prefix M3$;
export { M1$F, M2$F, M3$F };
Compact standard library
Compact의 표준 라이브러리는 import CompactStandardLibrary로 import할 수 있습니다.
표준 라이브러리는 Counter, Map, MerkleTree 등의 ledger 상태 타입과 함께
유용한 타입과 circuit를 정의합니다.
Top-level exports
특정 종류의 프로그램 요소, 즉 circuit, 프로그램 정의 타입, ledger 필드는 컨트랙트의 최상위에서 export할 수 있습니다. 이를 export하면 컨트랙트 외부, 즉 스마트 컨트랙트의 TypeScript 드라이버에서 보이게 됩니다.
컨트랙트의 최상위(단순히 모듈에서 export된 것이 아닌)에서 export된 circuit는 컨트랙트의 진입점입니다. (Compact 프로그램에는 "main" 진입점이 없지만, 공통 저장소를 공유하는 여러 진입점을 포함하는 라이브러리와 더 유사합니다.) 함수 오버로딩을 지원하기 위해 일반적으로 같은 이름의 여러 circuit가 허용되지만, 같은 이름의 circuit가 최상위에서 두 개 이상 export되면 정적 오류입니다. 제네릭 매개변수가 있는 제네릭 circuit가 최상위에서 export되는 것도 정적 오류입니다.
메인 파일의 최상위에서 export된 프로그램 정의 타입은 witness와 export된 circuit의 인수 및 반환 타입을 설명하는 데 사용할 수 있습니다. 이들은 제네릭 인수를 받을 수 있지만, 타입이 아닌 크기로 표시된 제네릭 인수는 export된 타입에서 제거됩니다. 예를 들어:
export struct S<#n, T> { v: Vector<n, T>; curidx: Uint<0..n> }
는 n 매개변수 없이 T 매개변수만 있는 TypeScript 타입으로 export됩니다. 즉:
export type S<T> = { v: T[]; curidx: bigint }
최상위에서 export된 ledger 필드 이름은 생성된 TypeScript ledger() 함수를 통해
컨트랙트 외부의 코드에서 직접 검사할 수 있습니다.
최상위에서 다른 종류의 바인딩을 export하는 것은 정적 오류입니다.
Include files
Compact는 프로그램과 모듈을 여러 파일로 분할하고 include 형식을 통해
합칠 수 있습니다. 다음 구문을 가지며, file은 포함할 파일의
파일시스템 경로명을 지정하는 문자열 리터럴입니다:
| include-form | ⟶ | include file ; |
file은 절대 경로명, 포함하는 파일의 디렉터리에 대한 상대 경로명, 또는 Compact 경로의 디렉터리 중 하나에 대한 상대 경로명일 수 있습니다. 검색 순서와 Compact 경로 설정 메커니즘에 대한 자세한 내용은 컴파일러 사용법을 참고하세요.
파 일이 존재하지 않거나 읽을 수 없으면 정적 오류입니다.
존재하고 읽을 수 있으면, 파일은 구문적으로 유효한 프로그램 요소의 시퀀스를
포함해야 하며, 이러한 요소는 include 형식 자리에 포함하는 파일에
존재했던 것처럼 처리됩니다.
Declaring witnesses for private state management
사용자의 비공개 상태는 스마트 컨트랙트의 TypeScript 드라이버에 의해 안전한 방식으로 유지되어야 하며, 컨트랙트의 공개 상태에 직접 저장해서는 안 됩니다. 그러나 컨트랙트는 때때로 비공개 상태의 일부에 대해 무언가를 proof해야 하며, 비공개 상태에 대한 업데이트를 유발해야 합니다.
스마트 컨트랙트의 TypeScript 드라이버는 일부 export된 circuit의 인수를 통해 비공개 상태의 일부를 컨트랙트에 제공할 수 있으며, export된 circuit의 반환 값에 기반하여 비공개 상태를 업데이트할 수 있습니다.
circuit는 연산 중에 witness를 통해 비공개 상태에 접근하거나 업데이트할 수도 있습니다. witness는 TypeScript 드라이버가 제공하는 콜백 함수입니다.
witness는 컨트랙트의 circuit에서 보이도록 선언되어야 합니다. witness 선언은 본문을 포함하지 않습니다. 구현은 TypeScript 드라이버가 제공하기 때문입니다.
| witness-declaration | ⟶ | exportopt witness id gparamsopt simple-parameter-list : type ; |
| simple-parameter-list | ⟶ | ( typed-id , ⋯ , typed-id ,opt ) |
witness 선언은 모듈이나 컨트랙트의 최상위의 프로그램 요소 중 어디에나 나타날 수 있습니다.
예를 들어:
witness W(x: Uint<16>): Bytes<32>;
는 컨트랙트가 16비트 부호 없는 값을 제공하고 추정상 비공개인 데이터의
32바이트를 수신하는 witness W를 정의합니다.
컨트랙트에서 witness 함수의 코드가 자신의 구현에서
작성한 코드라고 가정하지 마세요. 모든 DApp이 witness 함수에 대해
원하는 구현을 제공할 수 있습니다.
결과는 신뢰할 수 없는 입력으로 처리해야 합니다.
Declaring and maintaining public state
컨트랙트는 ledger 선언을 통해 공개 상태의 형태를 선언합니다.
각 ledger 선언은 컨트랙트가 공개 ledger에 저장할 수 있는 정보 하나를 정의합니다. 프로그램에 여러 ledger 선언이 나타날 수 있거나 없을 수도 있습니다. 모듈이나 컨트랙트의 최상위의 프로그램 요소 중 어디에나 나타날 수 있습니다.
ledger 선언은 ledger 필드 이름을 사전 정의된 ledger 상태 타입 중 하나에 바인딩합니다. 예를 들어:
import CompactStandardLibrary;
ledger val: Field;
export ledger cnt: Counter;
sealed ledger u8list: List<Uint<8>>;
export sealed ledger mapping: Map<Boolean, Field>;
모든 ledger 필드는 해당 ledger 상태 타입의 기본값으로 초기화됩니다. 컨트랙트 constructor가 있으면 기본값이 설정된 후에 실행되어 constructor가 기본값을 재정의할 수 있습니다.
Ledger state types
다음 ledger 상태 타입이 지원됩니다.
- 모든 일반 Compact 타입 T에 대한 T
Counter- 모든 Compact 타입 T에 대한
Set<T> - 모든 Compact 타입 K와 T에 대한
Map<K, T> - 모든 Compact 타입 K와 ledger 상태 타입 V에 대한
Map<K, V>(다음 섹션 참조) - 모든 Compact 타입 T에 대한
List<T> - 모든 n(1 < n <= 32)과 모든 Compact 타입 T에 대한
MerkleTree<n, T> - 모든 n(1 < n <= 32)과 모든 Compact 타입 T에 대한
HistoricMerkleTree<n, T> - 특정 ledger 상태에 의존하지 않는 ledger 연산에 대한 접근을 제공하는 특수 타입
Kernel
각 ledger 타입은 다음으로 호출할 수 있는 연산 세트를 지원합니다:
F.op(e, ..., e)
여기서 F는 필드 이름, op는 F의 ledger 상태 타입이 지원하는 ledger 연산의 이름, 각 e는 인수 표현식입니다.
Compact 타입 T로 선언된 ledger 필드는 암시적으로 Cell<T> 타입을 가지며,
read, write, reset_to_default를 포함한 여러 연산을 지원합니다.
예를 들어:
ledger F: Uint<16>;
export circuit putF(x: Uint<16>): [] {
F.write(disclose(x));
}
export circuit getF(): Uint<16> {
return F.read();
}
Cell ledger 상태 타입의 read 연산은 단순히 필드 이름에 대한 참조로
축약할 수 있으며, write(e) 연산은 F = e 형식의 할당으로 축약할 수 있습니다.
따라서 위의 코드를 다음과 같이 더 간단히 작성할 수 있습니다:
ledger F: Uint<16>;
export circuit putF(x: Uint<16>): [] {
F = disclose(x);
}
export circuit getF(): Uint<16> {
return F;
}
Counter 타입의 read 연산도 같은 방식으로 축약할 수 있으며,
increment 및 decrement 연산은 F += e 및 F -= e 형식의
할당으로 축약할 수 있습니다.
예를 들어:
import CompactStandardLibrary;
ledger F: Counter;
export circuit incrF(): [] {
F += 1;
}
export circuit decrF(): [] {
F -= 1;
}
export circuit getF(): Uint<64> {
return F;
}
적절한 ledger 상태 타입의 선택은 트랜잭션이 ledger 상태의 정확한
내용에 대한 의존성을 줄일 수 있으며, 따라서 proof가 온체인에서 확인될 때
트랜잭션이 거부될 가능성을 줄입니다.
예를 들어, 앞의 예제는 대신 Cell을 사용하여 작성할 수 있습니다:
ledger F: Uint<64>;
export circuit incrF(): [] {
F = F + 1 as Uint<64>;
}
export circuit decrF(): [] {
F = F - 1;
}
export circuit getF(): Uint<64> {
return F;
}
incrF와 decrF는 F의 값을 읽은 다음 연산하고 F에 다시 씁니다.
이 읽기는 트랜잭션을 필드의 현재 값에 커밋합니다.
트랜잭션 생성 시 F의 값이 27이면, proof가 온체인에서 확인될 때에도
F는 여전히 27이어야 합니다.
그러나 Counter 버전은 값을 읽지 않고 현재 값을 증가 또는 감소시키도록
요청하므로 그러한 제약 조건이 없습니다.
ledger 상태 타입과 연산에 대한 포괄적인 설명은 Compact ledger 데이터 타입 문서에서 찾을 수 있습니다.
Nested state types for Map
대부분의 경우 ledger 상태 타입은 다른 ledger 상태 타입 내에 중첩될 수 없습니다.
그러나 Map 키는 일반 Compact 타입이어야 하지만, Map 값은 일반 Compact 타입이나
ledger 상태 타입(Kernel 제외)을 가질 수 있습니다.
Map 값이 아닌 곳에서 ledger 상태 타입을 중첩하려는 시도는 정적 오류입니다.
Map 값 내에 Kernel 타입을 중첩하려는 시도도 정적 오류입니다.
다음은 두 레벨의 중첩을 보여주는 작은 예입니다:
import CompactStandardLibrary;
ledger fld: Map<Boolean, Map<Field, Counter>>;
export circuit initNestedMap(b: Boolean): [] {
fld.insert(disclose(b), default<Map<Field, Counter>>);
}
export circuit initNestedCounter(b: Boolean, n: Field): [] {
fld.lookup(b).insert(disclose(n), default<Counter>);
}
export circuit incrementNestedCounter1(b: Boolean, n: Field, k: Uint<16>): [] {
fld.lookup(b).lookup(n).increment(disclose(k));
}
export circuit incrementNestedCounter2(b: Boolean, n: Field, k: Uint<16>): [] {
fld.lookup(b).lookup(n) += disclose(k);
}
export circuit readNestedCounter1(b: Boolean, n: Field): Uint<64> {
return fld.lookup(b).lookup(n).read();
}
export circuit readNestedCounter2(b: Boolean, n: Field): Uint<64> {
return fld.lookup(b).lookup(n);
}
이 예에서,
fld는Boolean값에서Field값에서Counter까지의Map으로의Map에 바인딩됩니다initNestedMap은 특정 외부Map키에 대한 내부Map을 생성하는 데 사용할 수 있습니다initNestedCounter는 주어진 외부Map키와 내부Map키에 대한Counter를 생성하는 데 사용할 수 있습니다incrementNestedCounter1또는incrementNestedCounter2를 사용하여 주어진 외부Map키와 내부Map키에 대한 기존Counter를 증가시킬 수 있습니다readNestedCounter1또는readNestedCounter2를 사용하여 주어진 외부Map키와 내부Map키에 대한 기존Counter의 값을 읽을 수 있습니다.
참고:
-
중첩된 ledger 상태 값은 처음 사용하기 전에 초기화되어야 합니다.
default<T>구문은 기본 Compact 타입 값을 생성하는 데 사용할 수 있는 것처럼 기본 ledger 상태 타입 값을 생성하는 데 사용할 수 있습니다. 먼저 초기화하지 않고 중첩된 ledger 상태 값에 연산을 수행하려는 시도는 동적 오류입니다. -
ledger 상태 타입 값은 일급 객체가 아니므로, 중첩된 값에 접근할 때 전체 간접 참조 체인을 사용해야 합니다. 예를 들어, 다음은 정적 오류입니다:
export circuit incrementNestedCounter(b: Boolean, n: Field, k: Uint<16>): [] {
fld.lookup(b); // ERROR: 불완전한 간접 참조 체인
} -
마지막 연산이 기본 타입의 write, Counter 타입의 increment, Counter 타입의 decrement인 경우,
write,increment,decrement연산을=,+=,-=할당 구문으로 대체할 수 있으며, 이는 동일한 동작을 하는incrementNestedCounter1과incrementNestedCounter2로 설명됩니다. -
마지막 연산이 Counter 또는 기본 타입의 read인 경우 명시적
read()간접 참조를 생략할 수 있으며, 이는 동일한 동작을 하는readNestedCounter1과readNestedCounter2로 설명됩니다. -
편의를 위해 로컬 변수는 ledger 상태 타입의 기본값을 보유할 수 있으므로, 다음의
initNestedMap정의는 위의 것과 동일합니다.export circuit initNestedMap(b: Boolean): [] {
const t = default<Map<Field, Counter>>;
fld.insert(disclose(b), t);
}
Sealed and unsealed ledger fields
모든 ledger 필드는 ledger 필드 선언 앞에 선택적 수정자 sealed을
붙여 봉인으로 표시할 수 있습니다.
봉인된 필드는 컨트랙트 초기화 중에만 설정할 수 있습니다. 즉, 값은
컨트랙트 constructor(있는 경우)에 의해서만 수정할 수 있으며,
constructor 본문 내에서 직접 또는 constructor가 호출하는
헬퍼 circuit를 통해서만 수정할 수 있습니다.
sealed 키워드는 export 키워드(있는 경 우) 뒤에, ledger 키워드 앞에
와야 합니다. 다음 예와 같이:
sealed ledger field1: Uint<32>;
export sealed ledger field2: Uint<32>;
circuit init(x: Uint<32>): [] {
field2 = disclose(x);
}
constructor(x: Uint<16>) {
field1 = 2 * disclose(x);
init(x);
}
봉인된 ledger 필드가 export된 circuit에서 도달 가능한 코드에 의해 업데이트되면 정적 오류입니다.
Contract constructor
컨트랙트는 프로그램의 최상위에서 정의된 컨트랙트 constructor를 통해 초기화할 수 있습 니다.
| constructor-definition | ⟶ | constructor pattern-parameter-list block |
| pattern-parameter-list | ⟶ | ( typed-pattern , ⋯ , typed-pattern ,opt ) |
| typed-pattern | ⟶ | pattern : type |
constructor는 일반적으로 공개 상태를 초기화하는 데 사용되며 witness 호출을 통해 비공개 상태를 초기화하는 데에도 사용할 수 있습니다. 컨트랙트에 대해 최대 하나의 constructor를 정의할 수 있으며, 프로그램 최상위에만 나타나야 합니다. 즉, 모듈 내에서 정의할 수 없습니다. 모듈 내에서만 보이는 ledger 필드를 초기화하려면 constructor가 모듈에서 export된 circuit를 호출할 수 있습니다. 예를 들어:
module PublicState {
enum STATE { unset, set }
ledger state: STATE;
ledger value: Field;
export circuit init(v: Field): [] {
value = disclose(v);
state = STATE.set;
}
}
import PublicState;
constructor(v: Field) {
init(v);
}
각 constructor 매개변수에는 명시적 타입 주석이 있어야 합니다. 각 매개변수 패턴의 식별자 바인딩에서 입력의 해당 부분에 대한 각 변수 바인딩의 타입은 선언된 타입 구조의 해당 부분의 타입입니다.
constructor의 반환 타입은 항상 []입니다.
expr의 타입이 [] 이외의 것인 return expr;를 사용하여
다른 타입의 값을 반환하려는 시도는 정적 오류입니다.
Circuit definitions
Compact의 기본 운영 요소는 circuit입니다. 이는 대부분의 언어에서 함수와 밀접하게 대응하지만 영지식 circuit로 컴파일할 수 있도록 설계되었습니다. 함수와 비교하여 circuit의 주요 제한은 circuit가 직접적으로든 간접적으로든 재귀적일 수 없다는 것입니다.
Compact는 두 종류의 circuit를 지원합니다: 명명된 circuit와 익명 circuit. 명명된 circuit는 여기에서 설명하고, 익명 circuit는 Circuit 및 witness 호출에서 설명합니다.
명명된 circuit 정의는 다음 구문을 가집니다:
| circuit-definition | ⟶ | exportopt pureopt circuit function-name gparamsopt pattern-parameter-list : type block |
circuit 정의는 function-name을 주어진 매개변수, 반환 타입, 본문을 가진
circuit에 바인딩합니다.
선택적 export 수정자는 circuit 바인딩이 포함하는 모듈이나
circuit가 모듈 외부에서 정의된 경우 프로그램 자체에서 export되어야 함을 나타냅니다.
선택적 pure 수정자는
circuit가 순수함을 나타냅니다.
제네릭 매개변수가 있으면(gparams가 존재하고 비어 있지 않으면), circuit는 제네릭이며 호출 시점에서 특수화(제네릭 인수 제공)되어야 합니다.
circuit는 0개 이상의 매개변수를 취할 수 있습니다.
매개변수는 모두 인수 값의 선택된 부분에 바인딩될 식별자를 포함하는
패턴입니다.
가장 간단한 경우, 패턴은 식별자이며 인수 값 전체에 바인딩됩니다.
매개변수에 의해 설정된 바인딩은 circuit의 본문을 구성하는
블록 내에서(그리고 그 안에서만) 보입니다.
각 매개변수에는 명시적 타입 주석이 있어야 하며, circuit에 대한 모든 호출
시점에서 해당 인수 표현식의 타입은 해당 타입의 서브타입이어야 합니다.
각 매개변수 패턴의 식별자 바인딩에서 입력의 해당 부분에 대한 각 변수 바인딩의
타입은 선언된 타입 구조의 해당 부분의 타입입니다.
예를 들어, 아래 sumStruct의 본문에서:
struct S { x: Uint<16>, y: Uint<32> }
circuit sumStruct({x, y}: S): Uint<64> {
return x + y;
}
패턴 {x, y}에 의해 x에 대해 설정된 변수 바인딩은 Uint<16> 타 입을 가지고,
y에 대한 변수 바인딩은 Uint<32> 타입을 가집니다.
모든 명명된 circuit의 반환 타입은 명시적으로 선언되어야 하며, circuit가 해당 타입의 서브타입이 아닌 값을 반환할 수 있으면 정적 오류입니다.
본문은 circuit가 호출될 때마다 평가됩니다.
Pure and impure circuits
Compact circuit는 공개 상태(ledger를 통해) 또는 비공개 상태(witness를 통해)를 참조하거나 수정하지 않고 입력에서 출력을 계산하면 순수로 간주됩니다. 실제로, 컴파일러는 circuit의 본문에 ledger 연산, 비순수 circuit에 대한 호출, 또는 witness 호출이 포함되어 있으면 비순수로 간주합니다.
CompactStandardLibrary에 정의된 일부 외부 circuit는 witness이며,
이에 대한 호출은 호출자를 비순수로 만듭니다. 나머지는 순수로 간주되므로
이에 대한 호출은 호출자를 비순수로 만들지 않습니다.
Compact 프로그램은 circuit 정의 앞에 pure 수정자를 붙여
circuit가 순수하다고 선언할 수 있습니다. pure 수정자는 존재하는 경우
export 수정자 뒤에 와야 합니다. 예:
pure circuit c(a: Field): Field { ... }
export pure circuit c(a: Field): Field { ... }
pure 수정자의 유일한 효과는 컴파일러의 자체 분석에서 circuit가
실제로 비순수하다고 판단하면 선언을 오류로 플래그하는 것입니다.
pure 수정자를 통해 애플리케이션은 circuit가 PureCircuits 타입 선언과
Compact 컴파일러가 (올바른) Compact 프로그램에 대해 생성하는 TypeScript 모듈의
pureCircuits 상수에 있는지 확인할 수 있습니다.
Blocks
블록은 중괄호로 묶인 문장 그룹입니다:
블록은 단일 문장이 허용되는 모든 곳에서 단일 문장 대신 사용할 수 있으며,
이는 if 문의 "then" 및 "else" 부분이나 for 문의 본문에서
여러 문장을 평가할 수 있게 합니다.
모든 circuit 정의의 본문과 constructor(있는 경우)의 본문은 항상 블록입니다. 익명 circuit의 화살표 오른쪽은 블록이나 표현식일 수 있습니다.
블록 내의 문장은 중첩된 스코프를 차지합니다: 블록 내의 const 문에 의해
생성된 변수 바인딩은 블록 외부에서 보이지 않으며, 블록 외부에 있는
같은 이름의 식별자 바인딩을 섀도잉할 수 있습니다.
블록 내의 문장이 올바르게 타입이 지정되면 블록은 올바르게 타입이 지정됩니다.
블록은 문장을 순서대로 평가하여 평가됩니다.
Statements
Compact 문장은 Compact 표현식의 도움으로 계산이나 효과를 수행하는 데 사용됩니다.
문장은 값을 가지지 않거나(문장으로 사용되 는 표현식 시퀀스의 경우)
값이 무시됩니다.
따라서 문장의 타입에 대해 말할 필요가 없습니다.
그럼에도 불구하고, 각 문장에는 if 문의 테스트 표현식의 타입이 Boolean이어야
하는 것과 같은 타이핑 규칙이 있습니다.
이 섹션의 첫 번째 하위 섹션은 Compact 문장의 구문을 요약하는 문법 조각을 제시합니다. 나머지 하위 섹션은 다양한 종류의 문장에 대한 타이핑 및 평가 규칙을 설명합니다.
Syntax of statements
| stmt | ⟶ | if ( expr-seq ) stmt |
| | | stmt0 | |
| stmt0 | ⟶ | expr-seq ; |
| | | const cbinding , ⋯¹ , cbinding ; | |
| | | if ( expr-seq ) stmt0 else stmt | |
| | | for ( const id of start .. end ) stmt | |
| | | for ( const id of expr-seq ) stmt | |
| | | return expr-seq ; | |
| | | return ; | |
| | | block | |
| start | ⟶ | tsize |
| end | ⟶ | tsize |
위의 문법 조각은 문장(stmt)이 단일 팔(one-armed) if 문이거나
다른 종류의 문장(stmt0)임을 보여줍니다.
이 구조는 양팔(two-armed) if의 "then" 부분이 단일 팔 if일 수 없다는
제한을 강제하는 데 사용됩니다.
이 구조는 언어 문법에서 종종 모호하게 남겨지며, 각 "else" 부분을 가장 가까운 if 문과 연결하여 모호성을 해결한다는 별도의 메모가 있습니다. 여기서는 문법에서 해당 제약 조건이 명시적으로 표현됩니다.
Expression sequences used as statements
모든 표현식 시퀀스(expr-seq), 즉 순서대로 평가될 하나 이상의 표현식의 쉼표로 구분된 시퀀스는 문장으로 사용할 수 있습니다. 표현식 시퀀스는 자체 섹션에서 설명됩니다.
const statements
const 문은 로컬 변수 바인딩을 생성합니다.
모든 const 문은 다음 형식을 취합니다:
const cbinding , ...¹ , cbinding ;
여기서 각 cbinding은 다음 형식을 취합니다:
| cbinding | ⟶ | optionally-typed-pattern = expr |
| optionally-typed-pattern | ⟶ | pattern |
| | | typed-pattern |
const 문은 각 cbinding 하위 형식을 타이핑하여 타입이 지정됩니다.
cbinding 하위 형식은 = 오른쪽의 표현식을 타이핑하여 타입이 지정됩니다.
왼쪽에 타입 T가 선언되어 있으면, 표현식의 타입은 T의 서브타입이어야 합니다.
그렇지 않으면 정적 오류입니다.
패턴이 표현식의 타입과 다른 구조를 의미하면 정적 오류이기도 합니다.
예를 들어, 표현식의 값이 튜플인데 타입이 실제로 Field인 경우
패턴이 의미하면 정적 오류입니다.
각 패턴의 식별자 바인딩에서 입력의 해당 부분에 대한 각 변수 바인딩의 타입은
선언된 타입(있는 경우)의 구조의 해당 부분의 타입이고, 그렇지 않으면
추론된 타입의 해당 부분의 타입입니다.
예를 들어, 다음 코드에서 x에 대한 바인딩은 Boolean 타입을 가지고,
y에 대한 바인딩은 [Uint<64>, Uint<64>] 타입을 가집니다:
witness w(): [Boolean, [Uint<16>, Uint<32>]];
circuit foo(): [Uint<64>, Uint<64>] {
const [x, y]: [Boolean, [Uint<64>, Uint<64>]] = w();
return x ? y : [0, 0];
}
반면 다음에서 x는 여전히 Boolean 타입을 가지지만 y는 (추론된)
[Uint<16>, Uint<64>] 타입을 가집니다.
witness w(): [Boolean, [Uint<16>, Uint<32>]];
circuit foo(): [Uint<64>, Uint<64>] {
const [x, y] = w();
return x ? y : [0, 0];
}
섀도잉되는 경우를 제외하고, const 문에 의해 바인딩된 각 변수의 스코프는
const 문을 포함하는 가장 안쪽 블록 전체입니다.
그러나 바인딩이 주어지기 전에 참조해서는 안 됩니다.
그렇게 하려는 시도는 정적 오류입니다.
예를 들어, 아래 foo 정의의 본문 첫 줄에서 x에 대한 참조는 정적 오류입니다:
circuit foo(a: Uint<16>): Field {
const y = x + a;
const x = 7;
return y;
}
마찬가지로, 아래 const 문의 첫 번째 cbinding에서 x에 대한 참조도
정적 오류입니다:
const y = x, x = 7;
const 문은 cbinding 하위 형식을 순서대로 평가하여 각 cbinding에 의해
값이 주어진 변수가 뒤따르는 cbinding에서 참조할 수 있도록 합니다.
각 cbinding의 평가는 = 오른쪽 표현식의 값 v를 결정한 다음,
패턴과 구조 분해에서 설명한 대로
왼쪽의 패턴 p의 식별자에 v의 해당 부분에 대한 값을 부여하는 것입니다.
const로 바인딩된 변수는 블록 내에서 재사용할 수 없지만, 중첩된 블록의
const 바인딩이 섀도잉할 수 있습니다.
변수는 불변이지만, 여러 번 평가되는 코드 블록 내에 포함된 경우
같은 변수가 다른 시간에 다른 값을 가질 수 있습니다.
예를 들어 circuit의 본문이 두 번 이상 호출되는 경우가 그렇습니다.
if statements
if 문은 circuit 또는 constructor 본문의 문장을 통한
제어 흐름을 결정하는 데 사용됩니다.
단일 팔 if 표현식에는 "테스트 부분"(괄호로 묶인 expr-seq)과
"then 부분"(then-statement)이 있습니다:
if (expr-seq) then-statement
양팔 if 문에는 테스트 부분, then 부분, "else 부분"(else-statement)이 있습니다:
if (expr-seq) then-statement else else-statement
if 문의 타이핑은 expr-seq의 타입이 Boolean이어야 한다는 것만 요구합니다.
if 표현식의 평가는 먼저 expr-seq의 값 v를 결정하는 것입니다.
v가 true이면 then-statement가 평가됩니다.
그렇지 않으면 v는 false여야 하며, 이 경우 else-statement가
(있으면) 평가됩니다.
for statements
for 문은 값의 시퀀스를 순회하는 데 사용됩니다.
Compact에서는 대부분의 언어와 달리 반복 횟수를 항상 컴파일 타임에
결정할 수 있습니다.
즉, 반복 횟수는 상수 숫자 바운드 또는 상수 크기 객체의 크기로 바운드됩니다.
이 제한은 컴파일러가 유한한 proving circuit를 생성해야 하는
필요에 의해 동기 부여됩니다.
Compact는 두 종류의 for 문을 지원합니다.
첫 번째는 벡터, 튜플, 바이트 벡터를 순회하며 다음 형식을 취합니다:
for (const x of expr) stmt
이 종류의 for 문은 expr을 타이핑하고 Vector 타입,
벡터 타입을 가지는 튜플 타입,
또는 Bytes 타입인지 확인하여 타입이 지정됩니다.
이 종류의 for를 평가하려면 expr의 벡터, 튜플, 또는 바이트 벡터 값 v를
결정한 다음 v의 각 요소에 대해 x를 v의 각 요소 값에
차례로 바인딩하여 stmt를 한 번 평가합니다.
두 번째 형식은 부호 없는 정수 값의 범위를 순회하며 다음 형식을 취합니다:
for (const i of start .. end) stmt
이 형식에서 start와 end는 각각 리터럴 부호 없는 정수이거나 제네릭 자연수 매개변수에 대한 참조여야 하며, end는 start보다 크거나 같아야 합니다. 그렇지 않으면 정적 오류입니다.
이 형식은 항상 올바르게 타입이 지정됩니다.
이 종류의 for를 평가하려면 start(포함)부터 end(미포함)까지의 범위의
각 k에 대해 i를 k에 바인딩하여 stmt를 평가합니다.
return 문은 for 문 내에서 반환하는 데 사용할 수 없습니다.
따라서 stmt가 return 문이거나 stmt 내에 return 문이 나타나면
(익명 circuit 내에 중첩된 경우 제외) 정적 오류입니다.
반복은 map 및 fold 표현식을 통해서도
수행할 수 있습니다.
return statements
return 문은 가장 가까운 포함하는
익명 circuit(있는 경우)에서,
또는 그렇지 않으면 포함하는 constructor 또는 명명된 circuit에서
빠져나가 호출자에게 명시적 반환 값, 즉 다음 형식의 return에서
expr-seq의 값을 반환하는 데 사용할 수 있습니다:
return expr-seq;
또는 다음 형식의 return에서 기본값 []를 반환합니다:
return;
circuit 또는 constructor 본문은 명시적 return 문 없이도
빠져나갈 수 있습니다: 명시적 return 문으로 끝나지 않는 본문을 통한 모든 경로는
return;으로 끝나는 것처럼 처리됩니다.
선언된 반환 타입이 없는 익명 circuit에서 빠져나가면
return 문은 항상 올바르게 타입이 지정됩니다.
그렇지 않으면 expr-seq의 타입(또는 expr-seq가 없으면 [])이 예상 반환 타입의
서브타입이면 return 문은 올바르게 타입이 지정됩니다.
return 형식이 명명된 circuit 또는 선언된 반환 타입이 있는 익명 circuit에서
빠져나가면 예상 타입은 선언된 반환 타입이고,
constructor에서 빠져나가면 예상 반환 타입은 []입니다.
이 규칙의 함의는 [] 이외의 선언된 반환 타입을 가진 circuit에서
명시적 반환 값 없이 빠져나가면 정적 오류라는 것입니다.
return 문이 평가되면 expr-seq(있는 경우)가 평가되고,
circuit 또는 constructor는 후속 문장을 평가하지 않고
즉시 종료하며, 호출자에게 expr-seq의 값 또는 expr-seq가 없으면 []를 반환합니다.
Expressions
Compact 표현식은 값을 계산하거나 효과를 유발하거나 둘 다 하는 데 사용됩니다.
모든 Compact 표현식은 올바르게 타입이 지정되어야 합니다(정적 타입 오류가 없어야). 프로그램 내의 표현식에 정적 타입 오류가 포함되어 있으면 정적 오류입니다: Compact 컴파일러는 오류를 보고하고 프로그램에 대한 대상 코드 (TypeScript 또는 zkir)를 생성하지 않습니다. 올바르게 타입이 지정된 표현식의 정적 타입은 Compact 타입이거나 ledger 상태 타입입니다.
올바르게 타입이 지정된 모든 Compact 표현식은 값으로 평가되거나 동적 오류를 유발하며, 효과를 가질 수 있습니다. 표현식의 평가는 하위 표현식의 평가로 정의됩니다.
이 섹션의 첫 번째 하위 섹션은 Compact 표현식의 구문을 요약하는 문법 조각을 제공하고, 두 번째는 문법이 연산자의 우선순위와 결합성 규칙을 어떻게 반영하는지 논의합니다. 나머지 섹션은 각 종류의 표현식에 대한 타이핑 및 평가 규칙을 설명합니다.
Syntax of expressions
| expr-seq | ⟶ | expr |
| | | expr , ⋯¹ , expr , expr | |
| expr | ⟶ | expr0 ? expr : expr |
| | | expr0 = expr | |
| | | expr0 += expr | |
| | | expr0 -= expr | |
| | | expr0 | |
| expr0 | ⟶ | expr0 || expr1 |
| | | expr1 | |
| expr1 | ⟶ | expr1 && expr2 |
| | | expr2 | |
| expr2 | ⟶ | expr2 == expr3 |
| | | expr2 != expr3 | |
| | | expr3 | |
| expr3 | ⟶ | expr4 < expr4 |
| | | expr4 <= expr4 | |
| | | expr4 >= expr4 | |
| | | expr4 > expr4 | |
| | | expr4 | |
| expr4 | ⟶ | expr4 as type |
| | | expr5 | |
| expr5 | ⟶ | expr5 + expr6 |
| | | expr5 - expr6 | |
| | | expr6 | |
| expr6 | ⟶ | expr6 * expr7 |
| | | expr7 | |
| expr7 | ⟶ | ! expr7 |
| | | expr8 | |
| expr8 | ⟶ | expr8 [ expr ] |
| | | expr8 . id | |
| | | expr8 . id ( expr , ⋯ , expr ,opt ) | |
| | | expr9 | |
| expr9 | ⟶ | fun ( expr , ⋯ , expr ,opt ) |
| | | map ( fun , expr , ⋯¹ , expr ,opt ) | |
| | | fold ( fun , expr , expr , ⋯¹ , expr ,opt ) | |
| | | slice < tsize > ( expr , expr ) | |
| | | [ tuple-arg , ⋯ , tuple-arg ,opt ] | |
| | | Bytes [ bytes-arg , ⋯ , bytes-arg ,opt ] | |
| | | tref { struct-arg , ⋯ , struct-arg ,opt } | |
| | | assert ( expr , str ) | |
| | | disclose ( expr ) | |
| | | term | |
| term | ⟶ | id |
| | | true | |
| | | false | |
| | | nat | |
| | | str | |
| | | pad ( nat , str ) | |
| | | default < type > | |
| | | ( expr-seq ) | |
| struct-arg | ⟶ | expr |
| | | id : expr | |
| | | ... expr | |
| bytes-arg | ⟶ | tuple-arg |
| tuple-arg | ⟶ | expr |
| | | ... expr |
Precedence and associativity
표현식 문법의 구조는 연산자의 우선순위와 결합성을 모호함 없이 반영합니다.
expr-seq에서 term까지의 각 그룹은 우선순위 수준을 나타내며,
expr-seq는 expr보다 높은 우선순위, expr은 *expr0*보다 높은 수준,
이하 같은 방식입니다.
예를 들어, expr6(곱셈)은 expr5(덧셈)보다 높은 우선순위이므로
더 단단히 바인딩됩니다. 문법이 덧셈 표현식의 피연산자가 곱셈 표현식이 되는 것을
허용하지만 그 반대는 허용하지 않기 때문입니다.
예를 들어, x * y + z는 x * y와 z의 덧셈으로 파싱됩니다.
x와 y + z의 곱셈이 아닙니다.
여전히 x * (y + z)를 쓸 수 있는데, *expr6*이 term이 될 수 있고
("통과" 프로덕션을 통해), term이 괄호로 묶인 expr-seq가 될 수 있으며,
expr-seq가 expr이 될 수 있기 때문입니다.
문법은 연산자의 왼쪽 또는 오른쪽 피연산자 또는 둘 다가
더 높은 우선순위 수준이어야 하도록 요구하여 결합성 을 강제합니다.
구체적으로, 좌결합성은 오른쪽 피연산자가 더 높은 수준이어야 하면서
왼쪽 피연산자가 같은 수준이 되는 것을 허용하여 표현됩니다.
예를 들어, expr5 ⟶ expr5 + *expr6*는
왼쪽 피연산자가 더 높은 수준이어야 하므로 덧셈의 좌결합성을 강제합니다.
이는 예를 들어 x + y - z가 x + y에서 z를 빼는 것으로만 처리될 수 있고
x와 y - z의 덧셈이 아님을 의미합니다.
우결합성은 반대 방식으로 표현됩니다.
관계 연산자 <, <=, >=, >와 같은 비결합 연산자는 양쪽 피연산자가
엄격히 더 높은 수준이어야 하므로 a < b < c와 같은 체이닝을 방지합니다.
삼항 ?: 연산자의 중간 피연산자는 우선순위와 결합성 결정에 역할을 하지 않으며
어떤 종류의 표현식이든 될 수 있습니다.
(문법은 표현식 시퀀스가 될 수 있도록 허용할 수도 있었지만,
Compact는 TypeScript와 JavaScript를 따라 단일 표현식으로 제한합니다.)
Parenthesized expressions
Compact는 (e) 형식의 괄호 표현식을 허용하며,
e는 표현식이거나 더 일반적으로 표현식 시퀀스입니다.
괄호 표현식은 연산 순서를 제어하거나 단순히 기본 연산 순서를 명시적으로
만드는 데 사용할 수 있습니다.
괄호 표현식의 타입은 포함된 표현식 시퀀스의 타입이고, 값은 하위 표현식의 값입니다.
Expression sequences
표현식 시퀀스(위 문법의 expr-seq)는 하나 이상의 표현식의 쉼표로 구분된 시퀀스입니다.
표현식 시퀀스의 타입은 마지막 하위 표현식의 타입입니다. 다른 하위 표현식의 타입은 무시되며 어떤 방식으로든 제약되지 않습니다.
표현식 시퀀스는 하위 표현식을 왼쪽에서 오른쪽으로 평가하여 평가되며, 그 값은 마지막 하위 표현식의 값입니다. 다른 하위 표현식의 값은 무시됩니다. 이러한 다른 하위 표현식은 효과만을 위해 평가됩니다.
Literals
Compact는 불리언, 숫자, 문자열 리터럴 표현식의 구문을 가집니다.
__불리언 리터럴__은 예약어 true 또는 false 중 하나입니다.
불리언 리터럴의 정적 타입은 Boolean이고 값은 해당 불리언 값입니다.
__숫자 리터럴__은 10진수, 2진수, 8진수, 16진수 표기법으로 작성된 음이 아닌 정수입니다.
숫자 리터럴이 최대 부호 없는 값과 최대 필드 값을 초과하면 정적 오류입니다.
__문자열 리터럴__은 단순 문자열 리터럴이거나 pad 표현식일 수 있습니다.
문자열 리터럴은 바이트 벡터를 생성합니다. Compact에는 전용 String 타입이 없습니다.
Default values of a type
default<T> 표현식에서 T는 Compact 타입이거나 ledger 상태 타입이며,
정적 타입 T를 가지고 해당 타입의 기본값으로 평가됩니다.
컨트랙트 타입이나 Kernel ledger 상태 타입에 대해서는
기본값이 정의되지 않으므로, default<T>에서 T가 이러한 타입 중 하나이면
정적 오류입니다.
Variable references
변수 참조는 스코프 내의 변수 바인딩에 대한 식별자 참조입니다. 변수 참조의 타입은 참조하는 바인딩의 타입입니다.
Conditional expressions
Compact는 e0 ? e1 : e2 형식의
조건 표현식을 지원합니다.
*e0*의 타입은 Boolean이어야 합니다.
*e1*과 *e2*의 타입은 관련되어야 합니다.
조건 표현식은 먼저 *e0*를 평가하여 평가됩니다. *e0*의 값 v가 어떤 다른 하위 표현식이 평가될지 결정합니다. 평가 규칙은 *e1*과 e2 중 하나만 평가되도록 보장합니다.
Relational comparison expressions
관계 비교 표현식은 e1 op e2 형식을 취하며,
*e1*과 *e2*는 표현식이고 op는 Compact의 관계 연산자 중
하나입니다.
관계 연산자는 같음(==), 같지 않음(!=), 미만(<),
초과(>), 이하(<=), 이상(>=)입니다.
같음과 같지 않음은 *e1*과 *e2*의 타입이 관련되어야 합니다. 즉, *e1*의 타입이 *e2*의 타입의 서브타입이거나 *e2*의 타입이 *e1*의 타입의 서브타입이어야 합니다. 그렇지 않으면 정적 오류입니다.
미만, 초과, 이하, 이상은 *e1*과 *e2*의 타입이 부호 없는
정수 타입이어야 합니다. 그렇지 않으면 정적 오류입니다.
(Field 타입의 값은 이러한 연산자로 비교할 수 없습니다.)
결과의 타입은 Boolean입니다.
관계 비교 표현식 *e1 op e2*는 *e1*을 평가한 다음 *e2*를 평가한 다음 아래에서 설명하는 대로 결과 값을 비교하여 평가됩니다.
같음과 같지 않음
동등성은 다음 규칙에 따라 피연산자의 타입에 따라 달라집니다.
Boolean: 두 값이 모두true이거나 모두false이면 같습니다.Field및Uint타입: 정수 값이 같으면 같습니다.Bytes: 각 인덱스에서 해당 바이트가 같으면 같습니다.Vector및 튜플 타입: 각 인덱스에서 해당 값이 같으면 같습니다.- 구조체 타입: 해당 필드의 값이 같으면 같습니다.
- 열거형 타입: 같은 열거형 멤버이면 같습니다.
Opaque타입: JavaScript의 엄격한 동등성(===) 연산자에 따라 런타임 값이 같으면 같습니다.
그렇지 않으면 값은 같지 않습니다.
같음의 경우, 값이 같으면 관계 비교 표현식은 true로 평가되고,
그렇지 않으면 false로 평가됩니다.
마찬가지로, 같지 않음의 경우, 값이 같으면 false로, 그렇지 않으면 true로
평가됩니다.
미만, 초과, 이하, 이상
값은 해당 관계 연산에 따라 비교됩니다.
관계가 성립하면 true로, 그렇지 않으면 false로 평가됩니다.
Short-circuit logical expressions
Compact는 e1 op e2 형식의 단락 논리 표현식을 지원합니다.
여기서 *e1*과 *e2*는 표현식이고 op는 논리 연산자
or(||) 또는 and(&&) 중 하나입니다.
논리 표현식은 두 하위 표현식의 타입이 모두 Boolean이어야 합니다.
그렇지 않으면 정적 오류입니다.
논리 표현식 자체도 Boolean 타입을 가집니다.
*e1 op e2*의 평가는 먼저 *e1*을 평가한 다음:
||의 경우, v가true이면 *e2*는 평가되지 않으며, 전체 표현식의 값은true입니다. 그렇지 않으면 *e2*가 평가되고, 그 값이 전체 표현식의 값이 됩니다.&&의 경우, v가false이면 *e2*는 평가되지 않으며, 전체 표현식의 값은false입니다. 그렇지 않으면 *e2*가 평가되고, 그 값이 전체 표현식의 값이 됩니다.
이들은 첫 번째 피연산자에서 최종 값이 결정되면 두 번째 피연산자를 평가하지 않는 단락 연산자입니다.
Boolean negation expressions
Compact는 !e 형식의 단항 불리언 부정 표현식을 지원합니다.
e는 표현식입니다.
e의 타입은 Boolean이어야 하며, 그렇지 않으면 정적 오류입니다.
불리언 부정 표현식 자체도 Boolean 타입을 가집니다.
불리언 부정 표현식 !e는 e를 평가하여 값 v를 결정합니다.
v가 true이면 불리언 부정 표현식의 값은 false이고,
v가 false이면 불리언 부정 표현식의 값은 true입니다.
Binary arithmetic expressions
이항 산술 표현식은 e1 op e2 형식을 가지며,
*e1*과 *e2*는 피연산자 표현식이고 op는
Compact의 이항 산술 연산자 중 하나입니다.
이항 산술 연산자는 더하기(+), 빼기(-), 곱하기(*)입니다.
산술 표현식은 각 피연산자의 타입이 숫자 타입, 즉 Field, Uint,
또는 Field나 Uint에 대한 타입 별칭이어야 합니다.
타입 별칭을 제외하고, 결과의 타입은 피연산자의 타입에 따라 다음과 같습니다:
- 두 피연산자 모두
Field타입이면 결과는Field타입입니다. - 두 피연산자 모두
Uint타입이면, 즉 *e1*이Uint<0..m>타입이고 *e2*가Uint<0..n>타입이면, 결과의 타입은 연산에 따라 다릅니다:- 더하기의 경우, 결과는
Uint<0..m+n>타입이고, - 빼기의 경우, 결과는
Uint<0..m>타입이고, - 곱하기의 경우, 결과는
Uint<0..mxn>타입입니다.
- 더하기의 경우, 결과는
- 한 피연산자가
Field타입이고 다른 피연산자가Uint<0..n>타입이며 n-1이 최대 필드 값보다 작거나 같으면,Uint피연산자는 암시적으로Field로 캐스트되고 결과는Field타입입니다.
Uint 결과 타입의 산술 연산에서 결과의 바운드가
최대 부호 없는 정수(있는 경우)보다 크면
정적 오류입니다.
산술 표현식 *e1 op e2*의 평가는 먼저 *e1*을 평가한 다음
*e2*를 평가합니다. 그런 다음 피연산자 값에 정수 덧셈, 뺄셈, 곱셈이
사용됩니다.
오버플로우 및 언더플 로우 동작은 Field(어느 피연산자든 Field 타입) 연산과
Uint(두 피연산자 모두 Uint 타입) 연산에서 다릅니다:
Field산술 오버플로우와 언더플로우는 0 주위로 래핑됩니다. 즉, 결과가Field인 산술 연산의 결과는 실제 산술 값을 k로 나눈 나머지이며, 여기서 k는 최대 필드 값보다 1 큽니다.Uint덧셈과 곱셈은 오버플로우할 수 없습니다: 결과의 정적 타입은 항상 결과 값을 담을 만큼 충분히 큽니다.Uint뺄셈은 결과 값이 음수이면, 즉 *e2*의 값이 *e1*의 값보다 크면 동적 오류가 됩니다.
어느 피연산자의 타입이 Field 또는 Uint에 대한 구조적 타입 별칭이면
타입 검사와 평가 모두에서 해당 Field 또는 Uint와 동일하게 처리됩니다.
반면에, 어느 피연산자의 타입이 Field 또는 Uint에 대한 명목적 타입 별칭 T이면,
다른 피연산자의 타입도 T여야 하며, 결과의 타입도 T입니다.
평가는 두 피연산자가 기본 Field 또는 Uint 타입을 가진 것처럼 진행되며
값이 T로 다시 캐스트됩니다. T가 Uint<0..n> 타입에 대한 명목적 별칭이고
값이 n 미만이 아니면 동적 오류가 발생할 수 있습니다.
예를 들어:
new type Feet = Uint<32>;
circuit foo(x: Feet, y: Feet, scale: Uint<32>): Feet {
return (x + y) * (scale as Feet);
}
는 x와 y의 합에 scale을 곱한 값을 계산합니다.
이 계산의 값이 32비트에 맞으면 foo는 값을 반환합니다.
그렇지 않으면, 포함하는 최상위 circuit 또는 constructor가
Feet로의 캐스트가 실패했음을 나타내는 메시지와 함께 중단됩니다.
Tuple creation
새 튜플 값은 [tuple-arg, ..., tuple-arg] 형식의 표현식으로 생성되며,
tuple-arg, ..., tuple-arg는 0개 이상의 쉼표로 구분된 튜플 인수의 시퀀스입니다.
비어 있지 않은 시퀀스에는 선택적 후행 쉼표가 있을 수 있습니다.
각 튜플 인수는 표현식이거나 스프레드입니다.
튜플 인수가 타입 T의 표현식 e이면, 새 튜플에 타입 T의 단일 요소를 기여합니다: e의 값.
튜플 인수가 스프레드 ... e이면, e의 타입 T는 튜플 타입, Vector 타입,
또는 Bytes 타입이어야 합니다. 그렇지 않으면 정적 오류입니다.
튜플 생성 표현식의 타입은 각 튜플 인수가 순서대로 기여하는 타입의 튜플 타입입니다.
Byte vector creation
새 바이트 벡터는 Bytes [tuple-arg, ..., tuple-arg] 형식의 표현식으로 생성됩니다.
바이트 생성은 기본적으로 튜플 생성과 동일하지만,
기여하는 요소의 타입이 모두 Uint<8>의 서브타입이어야 하며(그렇지 않으면 정적 오류),
결과는 기여 요소 수가 n인 Bytes<n> 타입의 새 바이트 벡터입니다.
Tuple, vector, and byte vector references
Compact는 e[index] 구문을 통해 시퀀스 값(튜플, 벡터, 바이트 벡터)의
개별 요소에 대한 참조를 허용합니다. 여기서 e는 표현식이고 index는 숫자
리터럴, 제네릭 자연수 매개변수 참조, 또는 아래에서 설명하는 대로 컴파일 타임에
숫자 상수로 축소할 수 있는 표현식입니다.
e의 타입은 시퀀스 타입, 즉 튜플 타입, 벡터 타입, 또는 Bytes 타입이어야 합니다.
index의 타입은 모든 n에 대해 Uint<n>이어야 합니다.
index의 최종 상수 값은 타입에 의해 결정되는 시퀀스 값의 길이보다 작아야 합니다.
이러한 제약 조건 중 하나라도 위반하면 정적 오류입니다.
index의 부호 없는 정수 값은 다음 규칙에 의해 컴파일 타임에 계산 가능해야 하며, 그렇지 않으면 정적 오류입니다.
- 모든 index는 상수, 즉 숫자 리터럴이거나 제네릭 자연수 매개변수 참조일 수 있습니다.
- e의 타입이 벡터 타입을 가지는 튜플 타입이면, 아래의 벡터 타입 규칙이 적용되며 e는 벡터 타입을 가진 것으로 처리됩니다. e의 타입이 벡터 타입을 가지지 않는 튜플 타입이면 index는 상수여야 합니다.
- e의 타입이 벡터 또는 바이트 벡터 타입이면, index는 상수 값 표현식이어야 합니다.
시퀀스 참조의 값은 e를 평가한 결과의 i번째(0부터 시작) 요소의 값이며, 여기서 i는 index의 (최종적으로) 컴파일 타임 상수 값입니다.
Tuple, vector, and byte vector slices
시퀀스 값(튜플, 벡터, 바이트 벡터)을 슬라이싱하면 원래 값의 부분 시퀀스가 생성됩니다. 시퀀스 참조와 유사하지만 시퀀스 값에서 단일 값이 아닌 값의 시퀀스를 추출합니다.
슬라이스 표현식은 slice<k>(e, index) 형식을 취하며, slice는
키워드이고, k는 슬라이스의 고정 크기를 지정하는 상수 또는 숫자 제네릭 매개변수 참조이며,
e는 표현식이고, index는 숫자 리터럴, 제네릭 자연수 매개변수 참조, 또는
컴파일 타임에 숫자 상수로 축소할 수 있는 표현식입니다.
index + k가 시퀀스 값의 길이를 초과하면 정적 오류입니다.
예를 들어, getMiddle이 다음과 같이 정의되면:
export circuit getMiddle(x: Bytes<5>): Bytes<3> {
return slice<3>(x, 1);
}
호출
getMiddle(Bytes[17, 18, 19, 20, 21])
은 다음과 동등한 것으로 평가됩니다:
Bytes[18, 19, 20]
Structure creation
구조체 값은 T {struct-arg, ..., struct-arg} 형식의 구조체 생성
표현식으로 생성됩니다. 여기서 T는 구조체 타입 이름 S 또는
특수화된 제네릭 구조체 타입 S<garg, ..., garg>이고,
struct-arg, ..., struct-arg는 0개 이상의 쉼표로 구분된 구조체 인수의
시퀀스입니다.
구조체 인수는 세 가지 중 하나일 수 있습니다:
- 위치적 인수: 표현식 e의 형태를 취합니다.
- 명명된 인수: id
:e의 형태를 취하며, id는 필드 이름이고 e는 표현식입니다. - 스프레드 인수:
...e의 형태를 취하며,...는 리터럴 점 세 개(줄임표) 토큰이고 e는 표현식입니다.
아래 예제는 위치적 및 스프레드 필드 값의 사용을 보여줍니다:
struct S { a: Uint<32>, b: Boolean, c: Bytes<8> }
circuit f(x: Uint<32>, y: Boolean, z: Bytes<8>): S {
const s1 = S { c: z, a: x, b: y };
// s1은 위치적 구문 S { x, y, z }로도 생성할 수 있고
// 위치적과 명명된 필드 값의 혼합 S { x, c: z, b: y }로도 가능합니다.
const s2 = S { ...s1, b: true };
// s2는 스프레드 구문을 사용하여 생성됩니다. s2는 b가 true인 것을 제외하고
// s1과 같은 필드 값을 가집니다.
const s3 = S { ...s2, c: 'abcdefgh' };
// s3도 스프레드 구문을 사용하여 생성됩니다. s3는 c가 'abcdefgh'인 것을 제외하고
// s2와 같은 필드 값을 가집니다.
return s3;
}
Structure field access
구조체 필드 접근은 e.id 형식의 표현식이며, e는 구조체 타입 T의
표현식이고 id는 식별자입니다.
T에 id라는 이름의 필드가 없으면 정적 오류입니다.
e의 타입이 T인 구조체 필드 접근 e.id의 타입은 T의 id 필드의
타입입니다.
구조체 필드 접근 e.id의 값은 하위 표현식 e를 평가하고 결과 구조체의
id 필드의 값을 추출한 결과입니다.
e.id 형식의 일부 표현식은 열거형 멤버 선택이나
ledger 상태 연산일 수도 있습니다.
e.id는 e의 타입이 구조체 타입인 경우에만 구조체 필드 접근으로 인식됩니다.
Enumeration member selection
E.id 형식의 표현식에서 E가 열거형 타입의 이름이고 id가 식별자인 경우
열거형 멤버 선택입니다.
E에 id라는 이름의 멤버가 없으면 정적 오류입니다.
열거형 멤버 선택 E.id의 타입은 E입니다.
E.id는 상수입니다. 즉, 열거형 멤버 선택 E.id의 값은 E.id입니다.
Circuit and witness calls
circuit와 witness(통칭 함수)는
fun(e, ..., e) 형식의 표현식을 통해 호출됩니다. 여기서 fun은 함수
표현식이고 e, ..., e는 0개 이상의 쉼표로 구분된 인수 표현식의 시퀀스입니다.
함수 표현식 fun은 다음 형식 중 하나를 취할 수 있습니다:
| fun | ⟶ | id gargsopt |
| | | arrow-parameter-list return-typeopt => block | |
| | | arrow-parameter-list return-typeopt => expr | |
| | | ( fun ) | |
| arrow-parameter-list | ⟶ | ( optionally-typed-pattern , ⋯ , optionally-typed-pattern ,opt ) |
| return-type | ⟶ | : type |
가장 간단한 형태에서 fun은 witness 또는 명명된 circuit를 참조하는 식별자 id입니다. id가 제네릭 witness 또는 circuit를 참조하면 제네릭 매개변수를 통해 완전히 특수화되어야 합니다.
fun은 화살표 circuit라고도 하는 익명 circuit일 수도 있습니다.
이 형태는 매개변수 목록 다음에 선택적 반환 타입 : type,
화살표(=>), 블록이나 expr인 본문으로 구성됩니다.
circuit와 witness는 일급이 아니므로 fun은 변수 이름이나 임의의 표현식이 될 수 없습니다.
익명 circuit는 제네릭 매개변수를 가질 수 없습니다. 직접 호출되는 컨텍스트에서만 나타나므로, circuit를 즉시 특수화해야 하며, 이 경우 하나의 특수화만 존재할 수 있어 비제네릭 버전이 더 명확합니다.
함수 호출의 타입 검사는 기본 함수의 형태에 따라 달라집니다.
-
명명된 함수의 경우: 함수 이름은 오버로딩될 수 있습니다: 호출의 스코프에 같은 이름의 함수가 둘 이상 존재할 수 있습니다. 기본 함수가 이름인 호출에는 0개 이상의 후보 함수가 있을 수 있습니다.
후보 함수는 호출 사이트에서 제공된 제네릭 매개변수 값의 수와 종류 및 인수 표현식의 수와 타입과 반드시 호환되는 것은 아닙니다. 정확히 하나의 후보가 호환되면 해당 후보에 대한 호출이 이루어집니다. 정확히 하나의 호환 후보가 없으면 정적 오류입니다.
-
익명 circuit의 경우: 매개변수는 다음과 같이 타입 검사되거나 추론됩니다:
- 매개변수에 타입 주석이 있으면, 해당 인수 표현 식의 타입이 타입 주석의 서브타입이 아니면 정적 오류입니다.
- 매개변수에 타입 주석이 없으면, 매개변수의 타입은 해당 인수 표현식의 타입으로 추론됩니다.
호출은 인수 표현식을 왼쪽에서 오른쪽으로 순서대로 평가하여 평가됩니다. 그런 다음 circuit가 호출되면 circuit 본문의 문장이 매개변수 이름을 해당 인수 값에 바인딩하여 실행됩니다. circuit 호출의 값은 본문 실행에서 반환된 값입니다. witness가 호출되면 컨트랙트는 외부에서 제공된 witness 함수를 인수 값으로 호출합니다. witness 호출의 값은 witness 함수가 반환한 값입니다.
Map and fold expressions
Compact는 벡터, 벡터 타입을 가진 튜플(임의의 튜플이 아닌), 바이트 벡터에 대해 고차 연산인 _map_과 (왼쪽에서 오른쪽으로의) _fold_를 수행하는 표현식을 지원합니다.
Map 표현식은 map(fun, e, ...¹, e) 형식을 가지며, map은 키워드이고,
fun은 circuit 또는 witness이며, 각 e는 표현식입니다.
map 연산자는 표현식의 시퀀스 값의 해당 요소에 fun을 적용한 결과를
포함하는 벡터를 생성합니다.
Fold 표현식은 fold(fun, init, e, ...¹, e) 형식을 가지며,
fold는 키워드이고, fun은 circuit 또는 witness이며, init은
표현식이고, 각 e는 표현식입니다.
fold 연산자는 init의 값으로 시작하여 fun을 현재 값과 표현식의
시퀀스 값 의 해당 요소에 차례로 적용하여 업데이트되는 값을 축적합니다.
Type cast expressions
한 타입의 값은 허용될 때 e as T 형식의 캐스트를 통해 다른 타입으로
캐스트할 수 있습니다. e는 표현식이고 T는 타입입니다.
타입 T의 표현식 e를 다른 타입 U로 캐스트하는 효과는 관련된
특정 타입에 따라 달라집니다.
일부 타입 T와 U의 경우 캐스트가 허용되지 않으며, 이는 정적 오류입니다.
캐스트가 허용되더라도 타입 T의 일부 값은 타입 U의 값으로 표현할 수 없을 수 있습니다.
이 경우 e의 값에 대한 런타임 검사가 필요하며 동적 오류가 발생할 수 있습니다.
모든 (허용된) 캐스트 e as T의 타입은 당연히 T입니다.
TypeScript 캐스트 형식 <T>e는 Compact에서 지원되지 않습니다.
다음 규칙은 캐스트가 허용되는 시기, 동적 오류를 유발할 수 있는 시기, 런타임 변환이 필요할 수 있는 시기를 지배합니다.
Upcasts
- 업캐스트, 즉 타입에서 슈퍼타입으로의 캐스트
(예:
Vector<3, Uint<8>>에서Vector<3, Uint<16>>으로)는 항상 허용되며, 동적 오류를 초래하지 않고, 타입과 슈퍼타입이 같은 경우를 제외하면 런 타임 변환이 필요할 수 있습니다.
Downcasts for Field and Uint types
- 모든
Uint다운캐스트, 즉Uint<0..m>에서Uint<0..n>으로의 캐스트(m > n)는 허용되며, 런타임 검사가 필요하고, 런타임 변환이 필요할 수 있습니다.
Casts between Boolean and Field or Uint
Boolean에서Field또는 모든Uint타입으로의 캐스트는 항상 허용되며, 때때로 런타임 검사가 필요하고, 런타임 변환이 필요합니다(false를0으로,true를1로).Field또는 모든Uint타입에서Boolean으로의 캐스트는 항상 허용되며, 동적 오류를 유발할 수 없고, 간단한 런타임 변환이 필요합니다(0을false로, 다른 모든 값을true로).
Casts between enumeration types and Field or Uint
- 열거형 타입에서
Field또는 모든Uint타입으로의 캐스트는 허용되며, 때때로 런타임 검사가 필요하고, 런타임 변환이 필요할 수 있습니다. Field또는 모든Uint타입에서 열거형 타입으로의 캐스트는 허용되며, 때때로 런타임 검사가 필요하고, 런타임 변환이 필요할 수 있습니다.
Casts between Bytes and Field or Uint
Bytes<m>(m != 0)에서Field또는 모든Uint타입으로의 캐스트는 허용되며, 런타임 검사가 필요하고, 런타임 변환이 필요합니다.Field또는 모든Uint타입에서Bytes<m>(m != 0)으로의 캐스트는 허용되며, 런타임 검사가 필요하고, 런타임 변환이 필요합니다.
Casts between Bytes and Vector or tuple types
Bytes<m>에서Vector<m,Field>또는Vector<m,Uint<0..k>>(k >= 256)으로의 캐스트는 허용되며, 동적 오류를 유발하지 않지만 런타임 변환이 필요합니다.Vector<m,Uint<0..k>>(k <= 256)에서Bytes<m>으로의 캐스트는 허용되며, 동적 오류를 유발하지 않지만 런타임 변환이 필요합니다.
Ledger state operations
Compact 프로그램은 ledger에 대한 연산을 호출하여 공개 상태와 상호작용합니다. 두 가지 형태의 ledger 연산이 있습니다.
__Kernel 연산__은 특정 ledger 상태에 의존하지 않는 연산입니다.
id.op(e, ..., e) 형식의 표현식으로 호출할 수 있으며,
id는 특수 ledger 상태 타입 Kernel으로 선언된 ledger 필드의 이름이고,
op는 내장 kernel 연산의 이름이며,
e, ..., e는 0개 이상의 인수 표현식의 쉼표로 구분된 시퀀스입니다.
CompactStandardLibrary는 ledger 필드 이름 kernel을 ledger 상태 타입 Kernel으로
미리 정의하므로, 예를 들어 내장 self 연산은 circuit에서 다음과 같이 호출할 수 있습니다:
import CompactStandardLibrary;
circuit f(): ContractAddress {
return kernel.self();
}
__Ledger 상태 연산__은 컨트랙트의 선언된 공개 ledger 상태에 대한
연산입니다. 가장 간단한 형태에서 ledger 상태 연산은
F.op(e, ..., e) 표현식이며,
F는 ledger 필드 선언을 통해 선언된
ledger 필드 이름이고,
op는 ledger 상태 연산자의 이름이며, 각 e는 인수 표현식입니다.
F는 다른 ledger 상태 연산일 수도 있어, ledger 상태 연산의 결과가
자체적으로 ledger 상태 타입을 가질 수 있는 경우 ledger 상태 연산을
체이닝할 수 있습니다.
ledger 상태 연산의 정적 타입은 Compact ledger 데이터 타입 문서에 따른 해당 ledger 상태 연산의 반환 타입입니다. 반환 타입은 Compact 타입이거나 ledger 상태 타입일 수 있습니다. 반환 타입이 ledger 상태 타입인 ledger 상태 연산은 반드시 체이닝되어야 합니다. 즉, 즉시 다른 ledger 상태 연산의 대상이 되어야 합니다. 그렇지 않으면 정적 오류입니다.
Ledger state operation shorthand
Compact는 일반적인 ledger 상태 연산에 대한 여러 축약을 지원합니다.
가장 간단한 것은 F가 ledger 상태 필드 이름이거나 자체적으로 다른
ledger 상태 연산인 ledger 상태 연산 F.read()에 대한 축약입니다.
F의 타입이 read 연산을 지원하는 ledger 상태 타입이면,
F.read()는 간단히 F로 축약할 수 있습니다.
다른 축약은 모두 할당 문의 형태를 취하며 ledger 연산
F.write(e), F.increment(e), F.decrement(e)를 축약합니다.
F의 타입이 write 연산을 지원하는 ledger 상태 타입이면,
F.write(e)는 F = e로 축약할 수 있습니다.
마찬가지로, F의 타입이 increment 연산을 지원하면
F.increment(e)는 F += e로 축약할 수 있고,
F의 타입이 decrement 연산을 지원하면
F.decrement(e)는 F -= e로 축약할 수 있습니다.
축약에 대한 타이핑 및 평가 규칙은 축약하는 표현식과 동일합니다.
Assert
assertion은 assert(e, msg) 형식을 가지며, e는 표현식이고 msg는
문자열 메시지입니다.
e는 Boolean 타입이어야 하며, 그렇지 않으면 정적 오류입니다.
모든 assert 형식의 타입은 []입니다.
평가는 e를 평가하여 진행됩니다.
e의 값이 true이면(assertion 성공) assert 형식은 [] 값을 생성합니다.
그러나 e의 값이 false이면(assertion 실패) 동적 오류입니다. 즉,
assert 형식은 메시지 msg와 함께 포함하는 최상위 circuit 또는
constructor의 계산을 중단합니다.
각 assertion은 런타임에 검사되고 in-circuit으로 제약됩니다.
Explicit disclosure
비공개 데이터는 export된 circuit의 인수와 witness의 반환 값을 통해 Compact 컨트랙트에 들어올 수 있습니다. Compact는 선택적 공개를 지원하며, 컨트랙트가 컨트랙트의 트랜스크립트와 공개 상태에서 어떤 비공개 데이터를 공개할지, 어떻게 인코딩할지 결정합니다.
Compact 자체는 비공개 데이터의 성격과 공개 여부 및 방법에 대한 정보가 없으며, 그 결정은 컨트랙트 개발자에게 맡겨집니다. 그러나 Compact는 의도하지 않은 공개를 방지하기 위해 이러한 공개가 명시적으로 선언되도록 요구하여, 프라이버시가 기본값이고 공개가 예외입니다.
명시적 공개 선언 메커니즘은 간단합니다: 비공개 데이터(export된 circuit 인수,
witness 반환 값, 비공개 데이터에서 파생된 모든 것)의 공개는 비공개 데이터를
포함하는 값의 표현식을 공개 상태에 저장하거나, export된 circuit에서
반환하거나, 공개 상태에 저장되거나 export된 circuit에서 반환되는 것에
영향을 줄 수 있는 조건 표현식에서 사용하기 전에 disclose() 래퍼로 감싸서
인정해야 합니다.
예를 들어, 다음 컨트랙트는 export된 매개변수 x의 값을 ledger 필드 F에
저장할 때 공개를 명시적으로 선언하지 않고 비공개 데이터를 공개하려고 합니다:
ledger F: Uint<16>;
export circuit setf(x: Uint<16>): [] {
F = x;
}
이 프로그램을 컴파일하면 Compact 컴파일러 버전 1.0에서 다음 오류 메시지가 생성됩니다:
Exception: testfile.compact line 3 char 5:
potential witness-value disclosure must be declared but is not:
witness value potentially disclosed:
the value of parameter x of exported circuit setf at line 2 char 21
nature of the disclosure:
ledger operation might disclose the witness value
via this path through the program:
the right-hand side of = at line 3 char 5
이 경우 해결 방법은 간단합니다: x에 대한 참조를 disclose() 래퍼로 감쌉니다.
ledger F: Uint<16>;
export circuit setf(x: Uint<16>): [] {
F = disclose(x);
}
disclose() 래퍼는 그 자체로 공개를 유발하지 않습니다. 사실 래핑된 표현식의
값을 공개해도 괜찮다고 컴파일러에 알리는 것 외에는 아무런 효과가 없습니다.
동등하게, 래핑된 표현식의 값이 실제로 witness 데이터를 포함하든 아니든
포함하지 않는 것처럼 가장하라고 컴파일러에 알리는 것입니다.
컴파일러는 거짓 경보가 없도록 열심히 노력하며, 공개의 성격과 어떻게 발생하는지에 대한 정확하고 정밀한 정보를 제공합니다.
종종 같은 값이 다른 경로를 따라 공개되며, 공개의 성격은 특히 조건 표현식을 통한 간접 공개를 포함하는 경우 더 모호할 수 있습니다. 공개를 이해하고 해결하는 방법에 대한 자세한 조언은 이 문서의 범위를 벗어나지만, 몇 가지 주목할 만한 점이 있습니다:
- 주어진 공개 시점에서 여러 공개가 나열된 경우 보고된 첫 번째 공개 문제를 수정하는 것으로 종종 충분합니다.
- 가능할 때마다
disclose()래퍼는 실제 공개 사이트에 가능한 한 가까이 배치해야 합니다. - witness 데이터의 해시되거나 난독화된 변형을 공개할 때, 난독화되지 않은 입력의 공개를 방지하기 위해 입력과 공개 사이의 경로에서 난독화 후에 공개를 배치하세요.
자세한 내용은 Compact에서의 명시적 공개: Midnight "증인 보호 프로그램(Witness Protection Program)"을 참고하세요.
Runtime data representation
Compact 타입의 TypeScript 표현은 TypeScript에서의 표현에 정의되어 있습니다.
Compact는 TypeScript가 값을 표현하는 것과 정확히 같은 방식으로, 즉 일반 JavaScript 값으로 값을 표현합니다. 따라서 Compact Boolean은 런타임에 JavaScript boolean으로 표현되고, Compact 튜플은 JavaScript 배열로 표현되며, 열거형 값은 숫자로 표현됩니다.
타입 안전성을 유지하기 위해 Compact는 외부 호출자가 export된 circuit에 전달하거나 외부 witness에서 반환되는 값이 예상 타입을 가지는지 런타임에 확인합니다.
TypeScript target
컴파일되면 컨트랙트는 여러 아티팩트를 생성합니다. 핵심은 컨트랙트의 최상위에서 export된 circuit입니다. 이들은 두 범주로 나뉩니다: 순수 circuit와 비순수 circuit.
contract 디렉터리에서 컨트랙트의 의미론은 TypeScript로 인코딩되며,
index.js JavaScript 구현 파일과 index.d.ts 타입 선언 파일의 형태입니다.
대부분의 용도에서는 index.d.ts에서 제공하는 정보와 인터페이스에 의존하는 것이
권장됩니다.
공개 ledger를 건드리는 각 비순수 circuit에 대해, 영지식
prover/verifier 키 쌍과 proof 생성을 위한 지침도 생성됩니다.
이들은 출력 디렉터리의 keys와 zkir 하위 디렉터리에서 각각 찾을 수 있습니다.
Structure of exported TypeScript
export된 TypeScript는 모든 TypeScript 애플리케이션에서 컨트랙트와 상호작용하는 데 사용할 수 있는 여러 선언을 노출합니다.
컨트랙트는 TypeScript 모듈에서 다음을 export합니다:
- 컨트랙트의 최상위에서 export된 각 프로그램 정의 타입에 해당하는 TypeScript 타입
- 외부 witness가 컨트랙트를 인스턴스화하기 위해 만족해야 하는 형식을 설명하는
Witnesses<PS>타입 - 컨트랙트의 최상위에서 export된 비순수 circuit 세트를 설명하는
ImpureCircuits<PS>타입 - verifier 키가 있는 circuit 세트를 설명하는
ProvableCircuits<PS>타입 - 최상위에서 export된 순수 circuit 세트를 설명하는
PureCircuits타입 - 모든 export된 circuit 세트를 설명하는
Circuits<PS>타입 W의 인스턴스를 전달하여 구성할 수 있는Contract<PS = any, W extends Witnesses<PS> = Witnesses<PS>>클래스- 모든 순수 circuit를 순수 함수로 제공하는 상수
pureCircuits: PureCircuits객체 - 현재 ledger 상태에 대한 뷰를 제공하는
Ledger타입 Ledger타입의 생성자인ledger(state: runtime.StateValue | runtime.ChargedState): Ledger
Representations in TypeScript
Compact의 원시 타입은 TypeScript에서 다음과 같이 표현됩니다:
Boolean-booleanField- 런타임 바운드 검사가 있는bigintUint<n>/Uint<0..n>- 런타임 바운드 검사가 있는bigint[T, ...]- TypeScript 튜플 타입[S, ...]또는 런타임 길이 검사가 있는 TypeScript 배열 타입 S[]Bytes<n>- 런타임 길이 검사가 있는Uint8ArrayOpaque<"string">-stringOpaque<"Uint8Array">-Uint8Array
프로그램 정의 타입은 TypeScript에서 다음과 같이 표현됩니다:
enum인스턴스 - 런타임 멤버십 검사가 있는number- 필드
a: A, b: B, ...가 있는struct인스턴스 -{ a: A, b: B, ... }객체
다른 Opaque 타입은 현재 지원되지 않습니다.
Implementation-specific limits
Compact 컴파일러 버전 1.0은 특정 데이터 타입 값과 크기를 제한합니다:
Field값의 최대값은 52435875175126190479447740508185965837690552500527637822603658699938581184512입니다. 이 값은 ZK proving 시스템의 스칼라 필드 크기에 의해 결정됩니다.Uint값의 최대값은 452312848583266388373324160190187140051835877600158453279131187530910662655입니다. 이 값은 256k-1이며 여기서 k는Field에 맞는 바이트 수(31)입니다.- 벡터 또는 바이트 벡터의 최대 길이는 16777216입니다. 이 값은 컴파일러가 지나치게 큰 벡터 및 바이트 벡터 타입에 대해 작동하는 프로그램에서 무한 루프에 빠지는 것을 방지하기 위한 합리적인 상한으로 선택되었습니다.
이러한 제한은 컴파일러의 향후 버전에서 변경될 수 있습니다.
External resources
Compact formal grammar
여기를 참조하세요.
Compact keywords and reserved words
여기를 참조하세요.
Compact ledger state types and operations
여기를 참조하세요.
Runtime API
여기를 참조하세요.
Compiler usage
여기를 참조하세요.
Explicit disclosure in Compact
여기를 참조하세요.
Writing contracts in Compact
여기를 참조하세요.