제목: ERC2771
누군가가 트랜잭션을 보낼 때, 가스 요금을 지불해야 한다. 이는 해당 계정에 이더를 조금 예치해놔야 하는 것을 의미하고, 이는 생태계 확장에 있어서 굉장히 큰 걸림돌이 된다.
그렇기에 이를 막기 위해서 relayer라는 제 3자(혹은 CA)가 대신 가스비를 지불할 수 있게끔 만든 것이 ERC-2771이다.
보통 어떻게 진행되냐면.
EIP-712에서 사용하는 서명값 ecrecover을 통해서 쩐주가 아니라 돈 받는 사람 혼자서 온체인과 상호작용해서 트랜잭션을 가져오는 방식을 취한다.
주체는 4개고, ERC에서 표준으로 명명한 건 opt부분뿐임.
transaction signer: 이게 우리고요… 트랜잭션에 서명해서 gas relay로 보내면 됨
gas relay: 여기서 우리 트랜잭션에 가스를 추가해서 trusted Forwarder에게 보냄
trusted forwarder: 여기서 ecrecover써서 서명이 제대로 되어있는지 확인함. 이쯤되면 서명방정식 익히는 게ㄹㅇ 중요할 듯 진짜 개쌉 만능이네 ㅋㅋ;;
Recipient Contract: tf가 검증한 컨트랙트를 3가지를 추가로 검증한 다음에 execute시키는데, 3가지는 다음과 같다.
-Forwarder가 trusted되어 있는지. 쉽게 말해서 자기랑 연결된 forwarder가 tx를 전달해준건지를 검사한다.
-Transaction Signer address를 calldata 시작점으로부터 20바이트 읽어들여 해당 16진수를 트랜잭션 발송자인 sender로 사용한다.
-만약 위 두개의 조건을 충족시키지 못했을 경우, 해당 메시지는 그냥 단순 트랜잭션이라는 뜻이므로 sender를 msg.sender로 지정한다. (한마디로 tx 쏜 사람 말하는 거.) 왜 이런 제한조건이 붙었냐면,,,recipient 컨트랙트를 개발자가 따로 호출해서 변수를 수정하거나 해야 할 수도 있으니까,,, 이런 경우 이건 가스비 대신 낸거 확인 좀 해달라는 사용자의 메시지하고는 구분되어서 실행해야 하니까 ㅇㅇ
CF)
calldata에서 20바이트를 읽어오는 건 calldataload opcode기능을 사용한다. 해당 고수준 언어에 대응되는 저수준 언어의 동작과, calldata나 storage, memory같은 data저장공간 관리에 대한 내용은 다른 문서에서 다룰 예정이다.) 굳이 이 이야기를 꺼낸 이유는 밑에 구현된 코드에서 ASSEMBLY가 사용되기 때문이다;;;
recipient 컨트랙트 코드 보면 trusted forwarder주소는 immutable변수에다가 저장해놓거든요? 그래서 못바꿈. ㅇㅇ
오픈제플린이 구현해준 ERC-2771 탬플릿을 보면, ERC-2771 Forwarder에서
이 구문이 있는 이유는 간단하다. 해당 컨트랙트가 TrustedForwarder라는 것을 알려야 하는 상황이 실제 프로젝트에서 만들 때 필요하기 때문.
EIP-2771에서 가져온 문서의 번역본이다. 읽으면 바로 이해가 갈듯.
수신자 계약이 이 계약이 네이티브 메타 트랜잭션을 지원한다는 것을 아는 특정 프런트엔드에서 사용되지 않는 한 , 사용자에게 계약과 상호 작용하기 위해 메타 트랜잭션을 사용할 수 있는 선택권을 제공할 수 없습니다. 따라서 수신자가 메타 트랜잭션을 지원한다는 것을 세상에 알릴 수 있는 메커니즘이 필요합니다.
이는 특히 Web3 지갑 수준에서 메타 거래를 지원하는 데 중요합니다. 이러한 지갑은 사용자가 상호 작용하고자 하는 수신자 계약에 대해 반드시 아무것도 알지 못할 수도 있습니다.
수신자 는 다양한 인터페이스와 기능(예: 거래 일괄 처리, 다양한 메시지 서명 형식)을 갖춘 포워더를 신뢰할 수 있으므로 , 지갑에서 신뢰할 수 있는 포워더를 발견할 수 있도록 허용해야 합니다.
(약간 특정 기능을 구현한 컨트랙트들은 다 이렇게 이거 이 기능 구현되어있어요! 를 증명하는 함수를 하나씩 끼워넣는데, 이걸 위해서가 아닐까 싶다.)
istrustedforwarder는 가스 제한 5만 가스가 있다는데, 이거 사실 강제할 방법 없거든요? ㅋㅋ;;;;abstract contract이런거 쓰면 가능하기는 한데 보통 다른 이유들 때문에 인터페이스를 더 많이 사용해요. (자세한 내용은 밑의 CF에 적힌 인터페이스 vs abstract contract참조.)
사실 저희가 EIP만 볼 건 아니라서 ㅋㅋ;; 오픈제플린이 이거 탬플릿 가져다가 쓰라고 만들어놨으니까 한번 보죠.
사실 서명 탬플릿은 뭘 써도 상관은 없는데 eip712꺼 가져다가 쓰자고 권장하기도 했고 해서 얘들도 가져다가 쓴 거 같음.
들어가기 전에.
릴레이어가 포워드 요청을 제출하면 요청에 명시된 가스 금액의 최대 100%를 지불할 의향이 있어야 합니다. 이 계약은 이 가스에 대한 어떠한 종류의 보복도 구현하지 않으며 릴레이어가 서명자를 대신하여 실행 비용을 지불하는 대역 외 인센티브가 있다고 가정합니다. 종종 릴레이어는 사용자 인수 비용으로 간주되는 프로젝트에 의해 운영됩니다.
가스 비용을 지불하겠다고 제안함으로써 릴레이어는 공격자가 예상 대역 외 인센티브와 일치하지 않는 다른 목적으로 가스를 사용할 위험이 있습니다. 릴레이어를 운영하는 경우 대상 계약 및 기능 선택기를 허용 목록에 추가하는 것을 고려하세요. 특히 ERC-721 또는 ERC-1155 전송을 릴레이하는 경우 data
임의의 코드를 실행하는 데 사용될 수 있으므로 필드 사용을 거부하는 것을 고려하세요.
릴레이어를 사용자가 확실하게 믿을 수 있는지를 생각해야 함.
인터페이스 vs abstract contract
EIP(Ethereum Improvement Proposal)가 주로 인터페이스로 구현되는 이유는 여러 가지가 있습니다. 이에 대해 자세히 설명드리겠습니다:
- 유연성:
- 인터페이스는 구현 세부 사항을 강제하지 않습니다.
- 개발자들이 자신의 요구사항에 맞게 자유롭게 구현할 수 있습니다.
- 최소한의 제약:
- 인터페이스는 필요한 함수의 시그니처만 정의합니다.
- 이는 다양한 구현 방식을 허용합니다.
- 상호 운용성:
- 인터페이스를 통해 다른 컨트랙트들이 표준화된 방식으로 상호작용할 수 있습니다.
- 이는 생태계 전반의 호환성을 증진시킵니다.
- 다중 상속 지원:
- Solidity에서는 다중 상속이 제한적이지만, 여러 인터페이스를 구현하는 것은 가능합니다.
- 이를 통해 여러 EIP 표준을 동시에 준수할 수 있습니다.
- 가벼운 구조:
- 인터페이스는 구현 코드를 포함하지 않아 컨트랙트 크기를 최소화합니다.
- 이는 배포 비용과 가스 사용량을 줄이는 데 도움이 됩니다.
- 명확성:
- 인터페이스는 컨트랙트가 지원해야 하는 기능을 명확히 정의합니다.
- 이는 개발자들이 표준을 쉽게 이해하고 구현하는 데 도움이 됩니다.
- 버전 관리의 용이성:
- 인터페이스 변경은 기존 구현에 최소한의 영향을 미칩니다.
- 새로운 버전의 표준을 도입하기 쉽습니다.
- 테스트 용이성:
- 인터페이스를 기반으로 한 표준화된 테스트 suite를 만들 수 있습니다.
- 이는 다양한 구현체의 호환성을 검증하는 데 유용합니다.
- 추상 컨트랙트의 단점 회피:
- 추상 컨트랙트는 일부 구현을 포함할 수 있어, 특정 구현 방식을 강제할 수 있습니다.
- 이는 때때로 원하지 않는 제약이 될 수 있습니다.
- 커뮤니티 합의:
- 인터페이스는 기능의 ‘무엇’에 집중하고 ‘어떻게’는 개발자에게 맡깁니다.
- 이는 보다 넓은 커뮤니티의 합의를 얻기 쉽게 만듭니다.
예를 들어, ERC-20 토큰 표준은 인터페이스로 정의되어 있습니다:
이러한 접근 방식은 이더리움 생태계의 다양성과 혁신을 촉진하면서도 필요한 표준화를 제공합니다. 물론 일부 EIP는 추상 컨트랙트나 구체적인 구현을 포함할 수 있지만, 대부분의 경우 인터페이스를 통한 정의가 선호됩니다.