ERC-7401 부모 주도 NFT 중첩 표준 (Parent-Governed Non-Fungible Tokens Nesting)

Final · NFT·멀티토큰 · 2023-07-26 작성

NFT가 다른 NFT를 소유하는 부모·자식 중첩 관계를 ERC-721 확장으로 표준화합니다. 자식 토큰은 누구나 제안할 수 있지만 수락과 관리는 부모 토큰의 소유자만 할 수 있습니다.

왜 필요한가

ERC-721에서 토큰의 소유자는 외부 소유 계정(EOA)이나 스마트 컨트랙트뿐이어서 토큰이 토큰을 가질 수 없습니다. 그래서 여러 컬렉션의 NFT를 하나로 묶어 한 번의 거래로 파는 번들, 특정 NFT 아래에 관련 토큰을 모으는 수집, 멤버십 NFT 보유자에게 보너스 NFT를 그 멤버십 토큰 안으로 바로 발행하는 일, 투표용 NFT를 다른 회원의 NFT에 넣어 두는 위임 같은 쓰임새를 표준 방식으로 구현할 길이 없었습니다. 앞서 같은 문제를 다룬 ERC-6059는 제안이 다듬어지는 과정에서 한 매개변수가 명세에서 빠졌는데 인터페이스 ID에는 남아 있어, 인터페이스 ID와 실제 명세가 서로 맞지 않는 결함이 있었습니다. 이 표준은 NFT 사이의 부모·자식 관계 틀을 정의하면서 그 불일치를 바로잡은, ERC-6059와 기능적으로 동등한 대체 표준입니다.

구현해야 하는 것

심층 설명

루트 소유자와 직접 소유자

ownerOf는 ERC-721과 달리 소유자가 NFT이면 NFT가 아닌 주소가 나올 때까지 부모를 재귀적으로 조회해 루트 소유자를 돌려줍니다. directOwnerOf는 바로 위 소유자를 세 값으로 돌려주는데, 소유자 주소, 부모 토큰 ID, 소유자가 NFT인지 나타내는 불리언입니다. 직접 소유자가 NFT가 아니면 토큰 ID는 반드시 0이어야 합니다. 내부적으로 DirectOwner 구조체(tokenId, ownerAddress)가 이 정보를 담으며 소유자가 다른 토큰이면 ownerAddress는 부모 토큰 컬렉션 컨트랙트의 주소여야 합니다. 0 값이 NFT가 아님을 뜻하는 표시로 쓰이므로 토큰 ID는 절대 0이 될 수 없습니다.

제안-수락(propose-commit) 패턴

자식 토큰 추가는 반드시 제안-수락 패턴으로 이뤄집니다. 새 자식은 먼저 대기 배열에 들어가고 부모 토큰의 루트 소유자가 활성 배열로 옮겨야 합니다. 대기 배열은 스팸과 괴롭힘을 막기 위해 128칸으로 제한하는 것이 권장(SHOULD)됩니다. addChild는 직접 호출하면 안 되고 nestTransfer나 transferChild의 일부로 IERC7059 인스턴스에서만 호출되어야 하며 자식 컨트랙트의 directOwnerOf가 호출된 컨트랙트를 가리켜야 합니다. acceptChild는 인덱스 외에 기대하는 컨트랙트 주소와 토큰 ID를 함께 받아, 인덱스가 바뀌는 경쟁 상태에서 엉뚱한 토큰을 수락하는 것을 막습니다. rejectAllChildren의 maxRejections도 같은 원리로, 호출 준비 중에 새로 도착한 자식까지 거절되면 트랜잭션이 되돌려지도록 합니다.

부모 주도 권한 모델

중첩된 토큰의 진짜 주인은 부모 NFT와 그 루트 소유자입니다. 루트 소유자 또는 승인된 자는 자식에 대해 acceptChild, rejectAllChildren, transferChild를 실행할 수 있어야 합니다(MUST). transferFrom, safeTransferFrom, nestTransferFrom, burn은 토큰이 NFT에 소유되지 않은 경우에만 루트 소유자 또는 승인된 자가 실행할 수 있습니다. 토큰이 NFT에 소유되어 있으면 부모 NFT만 이 연산들을 실행할 수 있고 전송은 부모 토큰의 transferChild를 거쳐야 하며 이 함수가 목적지에 따라 자식 컨트랙트의 nestTransferFrom이나 safeTransferFrom을 호출합니다. transferChild만이 부모에서 자식을 제거하는 처리를 하므로 부모 컨트랙트의 상태 불일치를 막기 위한 제약입니다. 중첩된 토큰을 소각하려면 먼저 EOA로 꺼낸 뒤 소각해야 합니다.

transferChild의 다섯 가지 상태 전이

transferChild 하나로 다섯 가지 상태 전이를 처리합니다. 자식 토큰 거절(reject), 포기(abandon), 중첩 해제(unnest), EOA나 ERC721Receiver로 이전, 새 부모 토큰으로 이전입니다. 매개변수 조합으로 구분합니다. destinationId는 목적지가 토큰이 아니면 0이어야 하고 isPending은 자식이 대기 배열에 있는지(true) 활성 배열에 있는지(false)를 나타냅니다. to가 0x0 주소이면 자식 토큰의 소유 정보는 갱신되지 않습니다. 새 부모 토큰으로 보내는 경우 자식은 새 부모의 대기 배열에 들어가므로, 새 부모의 루트 소유자가 다시 수락해야 활성 배열에 놓입니다.

재귀 소각과 maxRecursiveBurns

burn(tokenId, maxRecursiveBurns)은 토큰을 소각할 때 그 자식 토큰까지 재귀적으로 함께 소각합니다. 소각할 자식이 지정한 최대치보다 많으면 실행이 반드시 되돌려져야 합니다(MUST). maxRecursiveBurns를 0으로 지정하면 해당 토큰만 소각을 시도해야 하고(SHOULD), 자식이 하나라도 있으면 되돌려져야 합니다(MUST). 반환값은 재귀적으로 소각된 자식 토큰의 수입니다. 이 장치가 없으면 깊이 중첩된 토큰을 소각할 때 의도보다 많은 토큰이 사라질 수 있기 때문에, 호출자가 소각 범위를 명시적으로 통제하게 한 것입니다.

인덱스 사용과 순환 중첩 금지

자식을 수락하거나 거절할 때 토큰 ID 대신 인덱스를 쓰는 이유는 가스 비용입니다. 토큰 ID로 찾으면 배열을 순회해야 해서 비용이 배열 크기에 비례하지만 인덱스는 고정 비용입니다. 매핑으로 인덱스를 내부 추적하는 구현도 시도했으나 자식 수락 비용이 20% 넘게, 발행 비용이 15% 넘게 늘어 표준에는 넣지 않고 확장으로 남겨 두었습니다. 또한 부모 토큰을 자신의 자식이나 그 하위 자식 안으로 중첩하는 것은 금지됩니다. 허용하면 소유 루프가 생겨 루프 안의 어떤 토큰도 관리할 수 없게 되기 때문입니다. 별도의 safe 중첩 전송 함수가 없는 것은 nestTransfer가 항상 목적지의 IERC7059 호환성을 확인해야 하므로 그 자체로 안전하기 때문입니다.

ERC-6059 대체와 ERC-721 호환성

이 표준은 ERC-6059를 대체(supersede)합니다. ERC-6059는 제안이 발전하면서 한 매개변수가 인터페이스 명세에서 빠졌는데 인터페이스 ID에는 포함되어 있어 둘이 일치하지 않았고 이 표준은 그 점만 고친 기능적으로 동등한 제안입니다. 이 인터페이스의 ERC-165 식별자는 0x42b0e56f입니다. ERC-721과의 유일한 비호환은 토큰 ID 0을 쓸 수 없다는 점이며 ownerOf가 루트 소유자를 돌려준다는 동작 차이가 있습니다. 토큰을 EOA나 ERC-721 Receiver가 소유한 경우에는 ownerOf가 ERC-721과 동일하게 동작합니다. 이 제안은 Final 상태로 확정된 표준입니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs