ERC-5750 메서드 확장용 마지막 bytes 인자 (General Extensibility for Method Behaviors)

Final · 기타 · 2022-10-04 작성

함수의 마지막 인자를 길이가 정해지지 않은 바이트 묶음(bytes)으로 두고 그 자리를 나중에 덧붙일 동작을 위한 확장 공간으로 쓰기로 약속하는 표준입니다.

왜 필요한가

이미 배포돼 쓰이고 있는 함수에 새 기능을 붙이려면 두 가지 길밖에 없습니다. 새 함수를 추가하거나, 기존 인자를 바꾸거나 더 얹는 것입니다. 어느 쪽이든 이름을 무엇으로 할지, 인자는 몇 개를 어떤 타입으로 둘지, 오프체인 서명은 지원할지 같은 질문에 프로젝트마다 다른 답을 내게 됩니다. 그 결과 비슷한 목적의 함수가 제각각 모양으로 늘어나고 지갑이나 다른 컨트랙트가 이를 한꺼풀로 다루기 어려워집니다. 이 표준은 그 답을 하나로 정합니다. 마지막 인자를 bytes 로 두고 그 자리를 확장 전용으로 비워 두면, 서명 첨부나 커밋 리빌(commit-reveal) 같은 새 동작을 함수 선언을 바꾸지 않고도 덧붙일 수 있습니다. EIP-721, EIP-1155 처럼 이미 이 모양을 갖춘 표준이 많아서 과거의 구현과도 앞으로 나올 확장과도 호환되는 것을 목표로 합니다.

구현해야 하는 것

심층 설명

컨트랙트가 아니라 메서드 단위로 준수를 판단합니다

대부분의 ERC 는 컨트랙트 전체가 명세를 따르는지 봅니다. 이 표준은 다릅니다. 함수 하나하나가 준수하는지 아닌지를 따집니다. 그래서 한 컨트랙트 안에 준수 메서드와 무관한 메서드가 섞여 있어도 문제가 없습니다. 원문 예시의 Foo 컨트랙트에는 methodName1 과 함께 firstNonRelatedMethod, secondNonRelatedMethod 가 있지만 준수 여부는 methodName1 에 대해서만 묻습니다. 머리말의 requires 항목에는 EIP-165 가 적혀 있지만 본문에서 그 사용법을 따로 설명하지는 않습니다.

적격 메서드와 준수 메서드

마지막 인자가 bytes 인 메서드는 모두 적격(eligible) 메서드입니다. 모양은 function methodName(type1 value1, type2 value2, ... bytes data) 입니다. 여기서 bytes 는 Solidity 의 동적 크기 바이트 배열로 해석해야 합니다. 적격 메서드 가운데 마지막 bytes 를 동작 확장용으로 지정한 것만 준수(compliant) 메서드입니다. 즉 선언 모양이 같아도 그 인자를 확장 목적으로 열어 두지 않았다면 준수가 아닙니다. 원문의 비준수 예시 3번은 methodName1 과 선언이 글자 하나까지 같지만 _data 로 동작을 확장하는 것을 허용하지 않아 비준수로 분류됩니다.

형제 오버로드가 있으면 빈 배열일 때 똑같이 동작해야 합니다

준수 메서드와 이름이 같고 마지막 bytes 만 빼고 앞쪽 인자가 완전히 같은 오버로드 메서드가 있으면 둘은 형제 관계입니다. 이때 준수 메서드에 빈 배열을 넘기면 형제 메서드와 동작이 똑같아야 합니다(MUST). 원문 예시 2번의 methodName2(uint256, address) 와 methodName2(uint256, address, bytes) 가 이런 짝입니다. 예시 4번은 선언은 같지만 _data 가 빈 배열일 때 형제와 다르게 동작하므로 비준수입니다. 이 규칙 덕분에 확장 인자를 모르는 기존 호출자도 결과가 달라질 걱정 없이 쓸 수 있습니다.

왜 동적 bytes 를 마지막에 두는가

동적 크기 bytes 를 쓰면 어떤 타입의 페이로드든 담을 수 있어 유연성이 가장 큽니다. 마지막 자리에 두는 이유는 Solidity 의 calldata 배치 방식과 맞기 때문입니다. 원문 Motivation 은 이 자리에 담을 수 있는 예로 transfer 나 vote 오버로드의 사유 문자열, 보증을 증명하는 값, 솔트, 논스, 커밋 리빌 방식의 커밋값을 들고 콜백으로 데이터를 그대로 넘기는 용도도 언급합니다. 커밋 리빌 사례로는 EIP-5732 를, 또 다른 사례로는 메서드 호출에 디지털 서명을 함께 보내는 경우를 듭니다.

이미 준수하는 기존 표준의 메서드

여러 ERC 는 이미 이 모양의 메서드가 있어 완전히 또는 부분적으로 이 표준을 따릅니다. 원문이 든 목록은 이렇습니다. EIP-721 의 safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data). EIP-1155 의 safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) 와 safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data). EIP-777 의 burn(uint256 amount, bytes calldata data) 와 send(address to, uint256 amount, bytes calldata data). 원문은 이 목록이 완전하지 않다고 밝힙니다.

마지막 bytes 가 기능에 쓰이는 메서드는 준수가 아닙니다

마지막 인자가 bytes 라도 그 값이 함수 기능 자체에 쓰이면 준수가 아닙니다. 원문은 두 사례를 듭니다. EIP-2535 의 diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) 와 EIP-1271 의 isValidSignature(bytes32 _hash, bytes memory _signature) 입니다. 각각 _calldata 와 _signature 가 기능에 관여합니다. 준수로 만들려면 두 방법 중 하나를 택해야 합니다(MUST). 기존 인자 모두의 뒤에 bytes calldata _data 를 덧붙인 오버로드를 새로 만들거나, 기존 마지막 bytes 인자의 용도를 확장 동작까지 허용하도록 느슨하게 바꾸는 것입니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs