본문으로 건너뛰기

Permissioned Asset Standard란?

주의

Permissioned Asset Standard는 현재 Testnet에서 사용할 수 있다. 아직 Mainnet에는 live 상태가 아니다.

Sui에서 store ability가 있는 객체는 소유자가 자유롭게 전송할 수 있다. BalanceCoin은 모두 store를 가지므로, 이를 보유하고 있다면 network 어디로든 restriction 없이 보낼 수 있다.

이는 general-purpose 자산에는 적합하지만, transfer control, 컴플라이언스 check 또는 issuer oversight가 필요한 regulated 자산에는 문제가 된다.

Permissioned Asset Standard (PAS)는 Accounts를 통해 자산 소유권을 proxy함으로써 이를 해결한다. Accounts는 자산을 보관하고, 모든 movement가 프로그래머블 승인 로직으로 gate되는 closed-loop system을 강제하는 객체이다.

TypeScript 패키지를 통해 PAS를 사용할 수 있으며, GitHub repo에서 더 자세히 알아볼 수 있다.

작동 방식

PAS는 각 지갑 주소(또는 객체 ID)마다 one-to-one derived Account를 생성한다. Account는 해당 주소를 대신해 자산을 보관하는 공유 객체이다. 소유자는 Auth proof를 통해 ownership을 증명할 수 있지만 자산을 자유롭게 전송할 수는 없다. 이는 자산이 PAS 모듈의 constraint와, 순차적으로 issuer가 하나 이상의 패키지에서 approval witness를 통해 정의한 규칙을 따르는 ownership proxy를 만든다.

자금이 이동할 때마다 동일 트랜잭션 안에서 해결되기 전에 미리 정의된 approval stamp(witness struct) set을 모아야 하는 hot potato 요청을 거친다. Hot potato 요청에는 drop 또는 store ability가 없으므로 요청이 해결되지 않으면 트랜잭션이 중단된다.

다음 diagram은 PAS가 없는 자산 소유권과 PAS를 사용하는 자산 소유권을 비교한다.

PAS 없음

Unrestricted 자산 소유권 without PAS

PAS 사용

Restricted 자산 소유권 with PAS

각 자산 타입은 액션별 승인에 필요한 witness를 정의하는 자체 정책을 가질 수 있다. 즉 서로 다른 자산은 완전히 다른 규칙을 따를 수 있다. 어떤 자산은 단일 컴플라이언스 stamp만 필요할 수 있고, 다른 자산은 여러 independent contract의 approval이 필요할 수 있다.

결과적으로 자산은 issuer가 정책 level에서 정의한 programmable, composable approval logic으로 모든 movement가 gate되는 closed system 안에 보관된다.

필요한 approval을 모으는 요청을 거치지 않고 managed Balance<C>를 system 밖으로 전송할 방법은 없다.

currency의 경우 이는 Move type level에서 강제된다.

  • Balance<C>balance::send_fundsbalance::redeem_funds를 사용해 Account 안에 저장된다(derived 객체 storage).

  • 자금을 이동하는 유일한 방법은 같은 트랜잭션 안에서 해결되어야 하는 요청 hot potato를 통하는 것이다.

  • resolution에는 Policy<Balance<C>>에 정의된 approval set과 일치하는 approval이 필요하다.

Object 모델 구조

다음 diagram은 PAS 객체 hierarchy를 보여준다.

Namespace (shared, singleton)
├── Account (@0xAlice) ← derived from (namespace_id, AccountKey(alice_addr))
├── Account (@0xBob) ← derived from (namespace_id, AccountKey(bob_addr))
├── Policy<Balance<C>> ← derived from (namespace_id, PolicyKey<Balance<C>>)
│ └── PolicyCap<Balance<C>> ← derived from (policy_id, PolicyCapKey)
└── Templates ← derived from (namespace_id, TemplateKey)

모든 객체는 derived 주소(sui::derived_object)를 사용하므로 deterministic하며 onchain lookup 없이 조회할 수 있다.

Namespace 구조

Namespace는 system의 root이다. 책임은 다음과 같다.

  • Accounts, 정책, templates의 주소 derive

  • emergency version blocking을 위한 Versioning state 보관

  • admin operation(version blocking, setup)을 gate하기 위한 UpgradeCap UID 유지

Account

Account는 Namespace UID와 소유자 주소에서 derive되는 공유 객체이다.

속성상세
CreationPermissionless. 누구나 어떤 주소에 대해서도 Account를 만들 수 있다.
OwnershipWallet 주소(ctx.sender()) 또는 객체(UID).
StorageBalance<C>를 객체 잔액으로 보관하거나, T를 Account UID의 객체로 직접 보관한다.
Derivationderived_object::claim(namespace_uid, AccountKey(owner)).

Policy

Policy<T>는 managed 자산 type T에 대해 해결 가능한 액션을 정의한다.

  • Required approvals: 액션 타입별(send_funds, unlock_funds, clawback_funds).

  • Clawback flag: issuer clawback이 허용되는지 여부.

  • Versioning: Namespace에서 sync된다. 패키지 version을 block할 수 있다.

currency의 경우 Policy<Balance<C>>를 통해 policy::new_for_currency(&mut namespace, &mut treasury_cap, clawback_allowed)를 생성한다. 여기에는 currency ownership의 proof로 TreasuryCap<C>가 필요하다.

PolicyCap

정책을 관리하는 capability이다. 정책 UID와 PolicyCapKey에서 one-to-one으로 derive된다. 다음 작업에 사용한다.

  • 액션별 required approval 설정 또는 업데이트

  • 액션 approval 제거(해당 액션의 요청을 unresolvable하게 만든다)

요청 pattern

PAS의 모든 state-changing operation은 요청 hot potato pattern을 따른다.

  1. Create: Account method가 data TRequest<Action<T>>로 wrap한다. 요청는 empty approval set으로 시작한다.

  2. Approve: 패키지가 request.approve(MyWitness())를 호출해 type-level proof로 요청에 stamp한다. 여러 패키지에서 여러 approval을 모을 수 있다.

  3. Resolve: resolution 함수가 수집된 approval이 정책의 required approval과 정확히 일치하는지 확인하고 요청 객체를 destroy한 뒤 액션을 실행하거나 data를 unwrap한다.

Request type

사용 가능한 요청 타입은 다음과 같다.

  • Request<SendFunds<T>>: 계정 간 transfer

  • Request<ClawbackFunds<T>>: issuer 자금 출금

  • Request<UnlockFunds<T>>: 자금 소유자로서 system에서 출금

Approval matching 방식

approval은 TypeName을 사용해 type identity로 match된다. approval set은 정책 required approval과 정확히 같아야 한다(same types, same count, VecSet insertion을 통한 same 주문).

정보

현재 version에서는 각 액션이 단일 approval witness만 지원한다. 여러 independent contract의 stamp를 요구하는 multi-approval support는 향후 release에 예정되어 있다.

예를 들어 contract에 정의된 TransferApproval witness struct의 경우:

// Policy requires: { TransferApproval }
// Request has: { TransferApproval } ← resolves
// Request has: { TransferApproval, ExtraApproval } ← aborts (count mismatch)
// Request has: { WrongApproval } ← aborts (type mismatch)

Balance tracking 방식

PAS는 Sui 주소 잔액을 사용한다.

잔액 저장 방식

다음 diagram은 잔액이 Account에 연결되는 방식을 보여준다.

Account (shared object)
└── UID
└── Balance<MY_COIN> stored via balance::send_funds(balance, account_object_address)

Balance는 Account struct의 필드로 저장되지 않는다. balance::send_funds를 사용해 Account 객체 주소로 자금을 보내고, balance::withdraw_funds_from_object(UID.withdraw_funds_from_object를 통해)를 사용해 다시 꺼내는 방식으로 Account UID의 객체 잔액으로 저장된다.

Balance flow 개요

다음 diagram은 예치 및 출금 path를 보여준다.

Deposit은 permissionless이다(누구나 어떤 Account에도 예치할 수 있다). Withdrawal은 internal(public(package))이다. PAS 모듈만 출금할 수 있으며, 요청을 통해서만 가능하다.

Wallet ownership과 객체 소유권 비교

Account는 지갑 주소 또는 객체가 소유할 수 있다.

다음 예시는 두 authentication method를 보여준다.

// Wallet-owned: proves ownership via transaction sender
let auth = account::new_auth(ctx);

// Object-owned: proves ownership via UID reference
let auth = account::new_auth_as_object(&mut my_object_uid);

Derived 객체 주소

모든 PAS 객체(Accounts, 정책)는 deterministic derived 주소를 사용한다. 이를 offchain에서 계산할 수 있다.

// Get the account address for an owner
let account_addr: address = namespace.account_address(@0xAlice);

// Get the policy address for a type
let policy_addr: address = namespace.policy_address<Balance<MY_COIN>>();

Security 모델 개요

PAS는 다음을 보장한다.

  • Closed loop: Managed 자산은 matching approval이 있는 요청을 거치지 않고 system을 떠날 수 없다.

  • Type-safe approvals: Approval witness는 TypeName으로 check된다. 다른 패키지의 approval을 forge할 수 없다.

  • Atomic resolution: Request는 hot potato이다. 같은 트랜잭션 안에서 해결되어야 하며, 그렇지 않으면 트랜잭션이 중단된다.

  • Deterministic addressing: 모든 객체는 derived 주소를 사용한다. hidden state나 non-deterministic 객체 creation이 없다.

PAS는 다음을 지원하거나 보장하지 않는다.

  • Access control: PAS는 누가 전송할 수 있는지 결정하지 않는다. 이는 approval witness를 통해 사용자의 contract가 담당한다.

  • Compliance 규칙: PAS는 규칙을 enforce하지 않는다. 사용자의 contract가 request.approve()를 호출하기 전에 이를 구현한다.

Trust boundaries

ComponentTrust level
PolicyCap<T> holderT의 approval 요구 사항를 변경할 수 있다.
TreasuryCap<C> holderBalance<C>에 대한 정책을 한 번 생성할 수 있다.
Account 소유자(Auth)자신의 Account에서 send 또는 unlock을 시작할 수 있다.
AnyoneAccount 생성, 예치, versioning sync를 수행할 수 있다.
Approval witness 패키지누가 요청을 승인할 수 있는지 제어한다.