ERC-3475 추상 스토리지 채권 (Abstract Storage Bonds)

Final · 토큰 · 2021-04-05 작성

채권처럼 상환 조건이 붙은 의무를 클래스와 논스라는 두 단계 ID로 토큰화하고 상환 조건과 메타데이터를 정해진 틀 없이 체인 위에 직접 저장하게 하는 표준입니다.

왜 필요한가

기존 토큰 표준으로는 상환 조건이 여러 개 붙은 채권을 표현할 수 없습니다. 예를 들어 탈중앙 거래소에 유동성을 맡기면 받는 LP 토큰(Liquidity Provider token)은 단순한 ERC-20 토큰이라서 복잡한 보상·상환 규칙을 체인 위에 담지 못합니다. 발행 시기마다 조건이 다른 채권을 만들려면 그때마다 별도 컨트랙트를 배포해야 하고 지갑이나 거래소 같은 외부 서비스는 각 채권의 잔액과 상환 조건을 일관된 방법으로 읽을 수 없습니다. 또 여러 채권을 묶어서 한 번에 옮기거나 2차 시장에서 쪼개어 거래하는 일도 표준화된 방식이 없었습니다. 이 표준은 여러 토큰 ID, 자유로운 구조의 온체인 메타데이터, 배치 처리를 한 인터페이스로 제공해 이런 문제를 풉니다.

구현해야 하는 것

심층 설명

클래스와 논스의 2단계 ID 구조

하나의 컨트랙트 안에서 채권은 classId와 nonceId 두 값으로 식별됩니다. 클래스는 채권의 종류를 나타내고 논스는 같은 클래스 안에서 발행 시기나 발행 조건의 차이를 나타냅니다. 같은 클래스에 속한 논스들은 클래스 메타데이터를 공유하지만 논스끼리는 대체 불가능하며 각 논스는 자기만의 메타데이터, 공급량, 상환 조건을 따로 가질 수 있습니다. 채권을 전송하면 그 메타데이터 전체가 새 소유자에게 넘어갑니다.

Transaction 구조체와 배치 처리

모든 상태 변경 함수는 Transaction 구조체의 배열을 받습니다. 구조체는 uint256 classId, uint256 nonceId, uint256 _amount 세 필드로 이루어지며 각각 채권 클래스, 발행 조건을 구분하는 논스, 수량을 뜻합니다. 덕분에 서로 다른 클래스와 논스의 채권 여러 개를 한 트랜잭션으로 발행, 전송, 상환, 소각, 승인할 수 있습니다. 실행이 끝나면 새 소유자가 각 채권의 액면가를 청구할 권리를 갖게 되며 이런 묶음 처리 방식은 2차 시장 거래 같은 용도에 유용합니다.

발행, 상환, 소각의 권한 규칙

issue()는 단일 주체가 발행해야 하며(MUST), 예시로는 담보 유동성 풀과 연동된 역할 기반 소유 컨트랙트가 제시됩니다. redeem()은 호출을 채권 발행자 컨트랙트로 제한해야 하고 만기 조건(종료 시각, 총 활성 유동성 등)이 충족된 뒤에만 실행되어야 하며(MUST), 은행 또는 2차 시장 마켓 메이커 컨트랙트만 호출해야 합니다(SHOULD). burn()은 컨트롤러 컨트랙트만 호출해야 하며(MUST) 역시 만기 조건 충족 후에만 실행됩니다. 여기서 은행(Bank)이란 유동성을 받은 뒤 채권을 발행, 상환, 소각하는 주체로, 보통 풀의 관리자 권한을 가진 단일 주체를 뜻합니다.

두 가지 승인 체계

approve()는 _spender에게 특정 (classId, nonceId)의 채권을 지정 수량만큼 쓸 권한을 주며 다시 호출하면 기존 허용량을 덮어씁니다. setApprovalFor()는 운영자(operator)에게 호출자의 채권 클래스 전체를 관리할 권한을 켜거나 끕니다. transferFrom()은 isApprovedFor(_from, _to, classId) 승인이 있어야 하고 transferAllowanceFrom()은 각 Transaction 항목마다 allowance(_from, msg.sender, classId, nonceId)가 있어야 합니다. 두 승인 함수 모두 은행이나 계정 소유자만 호출할 수 있어야 합니다.

공급량 집계와 만기 진행 조회

공급량은 네 가지로 나뉘어 집계됩니다. totalSupply()는 소각분과 상환분을 포함한 전체 공급량, activeSupply()는 상환되지 않은 활성 공급량, redeemedSupply()는 상환된 수량, burnedSupply()는 소각된 수량입니다. balanceOf()는 특정 주소의 잔액을 돌려주는데 상환된 채권도 포함합니다. getProgress()는 progressAchieved와 progressRemaining 두 값을 돌려주어 만기까지의 진행 상태를 보여 주며 기준은 유동성 비율이나 시간 등 구현에 따라 다를 수 있고 상환 조건은 내부 함수로 정의되어야 합니다(SHOULD).

메타데이터를 체인 위에 추상 저장

메타데이터는 title, _type, description 세 문자열을 가진 Metadata 구조체로 스키마를 정의하고 실제 값은 stringValue, uintValue, addressValue, boolValue, bytesValue 필드를 가진 Values 구조체에 담습니다. classMetadata()와 nonceMetadata()로 스키마를 읽고, classValues()와 nonceValues()에 인덱스 키를 넘겨 해당 값을 읽는 방식입니다. 예를 들어 symbol 항목이라면 클래스 ID를 키로 values 배열에서 해당 클래스의 심볼 문자열을 찾습니다. 고정된 저장 패턴을 강제하지 않으면서 외부 저장소 없이 모든 명세를 담을 수 있다는 점이 표준 이름(추상 스토리지)의 유래입니다.

이벤트와 기존 표준과의 호환성

채권 생애주기(발행, 상환, 전송)를 인덱싱할 수 있도록 Issue, Redeem, Transfer, burn, ApprovalFor 이벤트를 해당 트랜잭션마다 반드시 발생시켜야 합니다. 수량이 0인 동작은 이벤트에 포함하지 않아야 하고 _from이 0x0인 전송에는 Transfer 대신 Issue 이벤트를 써야 합니다. 이 표준의 인터페이스는 EIP-20, EIP-721, EIP-1155 인터페이스와 호환되며 기존 EIP-20 토큰도 민팅 권한을 이 표준을 내장한 은행 컨트랙트에 위임해 채권을 발행할 수 있습니다. 담보는 EIP-20 같은 대체 가능 토큰, EIP-721과 EIP-1155 같은 대체 불가능 토큰, 또는 이 표준으로 만든 다른 채권일 수 있습니다. 조회와 향후 업그레이드를 위해 별도의 은행 컨트랙트를 두는 것이 권장됩니다.

AMM 유동성 풀 통합

초기 자동화 마켓 메이커(AMM)는 토큰 쌍마다 별도 컨트랙트와 EIP-20 LP 토큰으로 유동성을 관리해서 풀 하나에 모이는 유동성이 줄고 불필요한 가스 비용과 슬리피지가 생겼습니다. 이 표준을 쓰면 클래스와 논스에 대응하는 유동성 데이터 구조 덕분에 모든 쌍을 담은 큰 유동성 풀을 만들 수 있고 채권 소유자의 유동성을 풀 안 토큰 쌍의 비율로 나타낼 수 있습니다. 풀 팩토리마다 따로 배포되던 EIP-20 LP 토큰이 하나의 채권으로 집계되어 단일 풀로 통합되는 효과입니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs