Skip to main content
For the complete documentation index, see llms.txt

OpenZeppelin contracts for Compact

OpenZeppelin Contracts for Compact는 Midnight을 위해 Compact으로 작성한 재사용 가능한 스마트 컨트랙트 빌딩 블록 라이브러리입니다. OpenZeppelin의 Solidity 컨트랙트 라이브러리 구조를 그대로 따르며, 커스텀 컨트랙트로 조합할 수 있는 토큰 표준, 접근 제어 패턴, 보안 기본 요소를 제공합니다.

라이브러리는 모듈식입니다. 베이스 컨트랙트를 상속하는 대신, 개별 모듈을 prefix와 함께 자신의 .compact 소스로 import하고 그 circuit을 빌딩 블록으로 호출합니다.

Experimental - alpha, not audited

라이브러리는 현재 v0.0.1-alpha.1 버전입니다. 프로젝트의 README에 따르면 보안 취약점에 대해 감사를 받거나 면밀히 검토된 적이 없으며, 유지보수자들은 다음과 같이 분명히 밝히고 있습니다. DO NOT USE IT IN PRODUCTION.

이 컨트랙트들은 프로토타이핑, 학습, Testnet 개발에만 사용하세요.

Prerequisites

라이브러리를 사용하기 전에 다음을 갖췄는지 확인하세요.

Compatibility

아래 표는 라이브러리가 대상으로 하는 Compact 컴파일러와 런타임 버전입니다. 통합하기 전에 로컬 toolchain과 대조해 확인하세요.

ComponentVersionNotes
Library versionv0.0.1-alpha.1알파. 빠른 변경이 예상됩니다.
Compact compiler0.29.0라이브러리의 CI 배지로 고정.
Compact language>= 0.21.0모든 소스 파일의 pragma 기준.
Token amountsUint<128> (Uint<256> 아님)Midnight 인코딩 한계. Uint<256>은 Compact 컴파일러가 지원하지 않습니다.
Midnight NetworkPreprod, Preview 전용Mainnet 대상 아님.
Compiler version mismatch

OpenZeppelin 라이브러리는 현재 컴파일러를 0.29.0에 고정해 두었지만, 공식 Midnight Toolchain은 그보다 새로운 버전을 출시했습니다. pragma >= 0.21.0이 소스 수준의 호환성을 유지해 주긴 하지만, 문제를 제보하기 전에 로컬의 compact compile --version을 확인하세요.

Modules

라이브러리는 모듈을 네 가지 계열로 나눕니다. 대부분의 모듈은 조합을 위해 Initializable(일회성 초기화 가드)과 Utils(zero-address 및 Either 헬퍼)를 import합니다.

Access control · src/access/

이 모듈들은 컨트랙트의 특정 circuit을 누가 호출할 수 있는지를 제어합니다. 가장 단순한 것부터 복잡한 것 순으로, 단일 소유자, 역할 기반, 그리고 그 프라이버시 보존 변형 순으로 나열되어 있습니다.

ModulePurposeOpenZeppelin docs
Ownable단일 소유자 권한. 소유자를 Either<ZswapCoinPublicKey, ContractAddress>로 저장합니다. 소유권 이전, 포기, 그리고 contract-address 소유자를 위한 safe/unsafe 변형을 지원합니다.Guide · API
AccessControladmin 계층을 갖춘 역할 기반 권한. 역할은 Bytes<32> 식별자입니다. 각 역할에는 부여와 회수를 제어하는 설정 가능한 admin 역할이 있습니다.Guide · API
ShieldedAccessControl프라이버시 보존형 역할 기반 접근 제어. 역할 commitment를 MerkleTree<20>에 저장하고 nullifier로 회수하므로, 역할 소속을 영지식으로 증명할 수 있습니다.아직 OpenZeppelin 문서 사이트에 없습니다. 소스를 참고하세요.
ZOwnablePK프라이버시 보존형 단일 소유자 권한. 소유자를 public key 대신 commitment 해시로 저장하므로, 온체인 데이터가 소유자가 누구인지 드러내지 않습니다.Ownable과 함께 문서화됨

Security · src/security/

이 모듈들은 컨트랙트에 라이프사이클 및 운영 가드를 적용합니다.

ModulePurposeOpenZeppelin docs
Initializable일회성 초기화 가드. 컨트랙트가 두 번 이상 초기화되는 것을 막습니다. 다른 대부분의 모듈이 이것에 의존합니다.Guide · API
Pausable비상 정지 스위치. assertNotPaused() 뒤에서 circuit 실행을 통제합니다. _pause()_unpause()는 직접 만든 권한 로직으로 감싸세요.Guide · API

Tokens · src/token/

이 모듈들은 Compact에 맞게 조정한 표준 토큰 인터페이스를 구현합니다.

ModulePurposeOpenZeppelin docs
FungibleTokenERC-20 방식의 대체 가능 토큰. transfer, approve, transferFrom, mint, burn을 지원합니다. 잔액은 Uint<128>을 사용합니다(Uint<256> 아님 - Compatibility 참고). safe·unsafe transfer 변형을 포함합니다.Guide · API
NonFungibleTokenERC-721 방식의 대체 불가능 토큰. 소유권, 승인, operator 승인, token URI를 추적합니다. 토큰 ID는 Uint<128>을 사용합니다.Guide · API
MultiTokenERC-1155 방식의 멀티 토큰. 하나의 컨트랙트에서 여러 토큰 유형을 관리합니다. 일괄 연산은 지원하지 않습니다(Compact에 동적 배열이 없습니다).Guide · API

Utilities · src/utils/

타입 안전성과 입력 검증을 위한 헬퍼 함수입니다.

ModulePurposeOpenZeppelin docs
UtilsEither<ZswapCoinPublicKey, ContractAddress> 값을 다루는 순수 헬퍼. zero 검사, 동등성 검사, 그리고 조작된 입력 공격을 막는 canonicalize를 포함합니다.Guide · API

Installation

라이브러리는 현재 Git submodule 형태로 제공됩니다. OpenZeppelin 팀은 프로젝트 README에서 밝혔듯이 이를 npm에 게시할 계획입니다.

새 프로젝트에 라이브러리를 추가하려면 submodule로 클론한 뒤 컨트랙트를 컴파일하세요.

mkdir my-project && cd my-project
git init
git submodule add https://github.com/OpenZeppelin/compact-contracts.git
nvm install
yarn
SKIP_ZK=true yarn compact

Compose a contract

개별 모듈을 prefix와 함께 .compact 소스로 import한 뒤, 그 circuit을 빌딩 블록으로 호출하세요. prefix Pausable_; 관례에 유의하세요. prefix 자체가 _로 끝납니다. 내부적으로 underscore가 붙은 _pause circuit을 호출하면 Pausable__pause()가 되는데, 이중 underscore는 의도된 것입니다.

pragma language_version >= 0.21.0;

import CompactStandardLibrary;
import "./compact-contracts/node_modules/@openzeppelin/compact-contracts/src/access/Ownable"
prefix Ownable_;
import "./compact-contracts/node_modules/@openzeppelin/compact-contracts/src/security/Pausable"
prefix Pausable_;
import "./compact-contracts/node_modules/@openzeppelin/compact-contracts/src/token/FungibleToken"
prefix FungibleToken_;

constructor(
_name: Opaque<"string">,
_symbol: Opaque<"string">,
_decimals: Uint<8>,
_recipient: Either<ZswapCoinPublicKey, ContractAddress>,
_amount: Uint<128>,
_initOwner: Either<ZswapCoinPublicKey, ContractAddress>,
) {
Ownable_initialize(_initOwner);
FungibleToken_initialize(_name, _symbol, _decimals);
FungibleToken__mint(_recipient, _amount);
}

export circuit transfer(
to: Either<ZswapCoinPublicKey, ContractAddress>,
value: Uint<128>,
): Boolean {
Pausable_assertNotPaused();
return FungibleToken_transfer(to, value);
}

export circuit pause(): [] {
Ownable_assertOnlyOwner();
Pausable__pause();
}

export circuit unpause(): [] {
Ownable_assertOnlyOwner();
Pausable__unpause();
}
Why import through node_modules

OpenZeppelin 팀은 공유 의존성 간의 상태 충돌을 피하기 위해, submodule 경로 대신 compact-contracts/node_modules/@openzeppelin/compact-contracts/...를 통해 import할 것을 권장합니다. 자세한 내용은 프로젝트 README를 참고하세요.

Compile

Compact 컴파일러를 실행해 컨트랙트로부터 TypeScript 바인딩과 ZK circuit 아티팩트를 생성합니다.

compact compile MyContract.compact artifacts/MyContract

컴파일에 성공하면 각 circuit과 그 proving-system 크기가 함께 표시됩니다.

Compiling 3 circuits:
circuit "pause" (k=10, rows=125)
circuit "transfer"(k=11, rows=1180)
circuit "unpause" (k=10, rows=121)
Overall progress [====================] 3/3

k는 domain 크기(circuit이 2^k개의 row에 배치됩니다)이고, rows는 그중 circuit이 실제로 사용하는 row 수입니다. circuit이 작을수록 증명과 검증이 빠릅니다. 더 자세한 설명은 OpenZeppelin의 ZK Circuits 101을 참고하세요.

Patterns to know

다음 패턴들은 라이브러리 전반에 적용되며, 컨트랙트를 어떻게 구성할지에 영향을 줍니다.

  • 위임을 통한 모듈식 조합: OpenZeppelin의 Module/컨트랙트 패턴은 이 라이브러리가 따르기를 기대하는 공식 구조입니다. 각 모듈은 두 종류의 circuit을 export합니다. 하나는 leading underscore가 없는 external circuit(transfer, approve 등)으로, 그대로 노출해도 안전합니다. 다른 하나는 leading underscore가 붙은 public circuit(_mint, _burn 등)으로, 직접 만든 컨트랙트 로직으로 감싸는 조합용 빌딩 블록입니다. internal 헬퍼는 export되지 않은 채로 남습니다. 컨트랙트는 모듈을 prefix와 함께 import해 그 circuit들을 연결합니다. 생성자 밖에서 initialize()를 호출하거나 circuit을 그대로 재export해서는 안 됩니다.

  • prefix를 통한 조합: import ... prefix X_; 문법은 각 모듈에 네임스페이스를 부여해, 식별자 충돌 없이 여러 모듈이 한 컨트랙트에 공존할 수 있게 합니다. 이름이 _로 시작하는 circuit은 호출 지점에서 X__name 형태로 나타납니다.

  • 초기화는 명시적이다: 대부분의 모듈은 Initializable을 import하며, 생성자에서 명시적인 initialize 호출을 요구합니다. Initializable_assertNotInitialized()Initializable_assertInitialized()가 이를 강제합니다. initialize 전에 다른 circuit을 호출하면 런타임에 실패합니다.

  • contract-address 수신자는 제한된다: Compact은 아직 컨트랙트 간 호출을 지원하지 않으므로, 모든 모듈의 safe 경로(예: Ownable.transferOwnership, AccessControl.grantRole, FungibleToken.transfer)는 ContractAddress 수신자를 거부합니다. 각 모듈은 safe 변형과 함께, 위험이 드러나도록 명시적으로 이름 붙인 _unsafe* circuit을 짝지어 제공합니다. 유지보수자들은 Compact이 컨트랙트 간 호출을 지원하게 되면 unsafe 변형을 deprecate할 계획입니다.

  • 호출자 권한 확인에는 두 가지 방식이 있다: OwnableAccessControlownPublicKey()를 직접 사용해 호출자를 확인합니다. ZOwnablePKShieldedAccessControl은 witness에서 유도한 비밀을 해싱·instance salt와 결합해 commitment로 호출자를 검증하며, public key를 온체인에 절대 노출하지 않습니다. 컨트랙트가 소유자나 역할 보유자가 누구인지 드러내지 않아야 한다면 프라이버시 보존형 변형을 권장합니다.

  • Either<ZswapCoinPublicKey, ContractAddress>는 표준 주소 타입이다: Utils.canonicalize 헬퍼는 Either에서 사용하지 않는 쪽을 0으로 만들어, 양쪽 모두에 데이터가 담기는 조작된 입력 공격을 막습니다.

Resources

자세한 내용은 다음 링크를 참고하세요.

Reporting issues

라이브러리 코드 관련 문제는 OpenZeppelin 트래커에 제보하세요. 보안 사항 제보는 security@openzeppelin.com으로 이메일을 보내세요.

이 문서 페이지 관련 문제는 Midnight docs 저장소에 제보하세요.