Folks Finance사의 개발문서를 보고 Cross-Chain Lending Protocol이 어떻게 이루어졌고 구상되었는지 알아보기 위해 읽고 정리해보았다.


Cross-Chain Messaging Architecture


Cross-Chain Lending Protocol은 Wormhole 또는 Chainlink CCIP를 통해 메세지를 전송하는 것을 지원한다.


사용자가 Spoke 체인에서 거래를 시작할 때, 메세지를 중계할 GMP(Generic Messaging Protocol)를 지정할 수 있다. 일부 GMP는 모든 작업은 지원하지 않을 수 있고, 이 경우 사용자는 선택에 제한을 받게 된다. 프론트엔드는 사용자 경험이 더 많은 GMP를 기본적으로 선택하도록 한다고 한다.


설계는 유연성을 갖추어 최신 버전의 GMP로 업그레이드하고, 취약한 GMP를 제거하고 새로운 GMP를 추가할 수 있도록 했다. 또한 통합의 용이성도 고려되었다.



BridgeMessenger


추상 계약인 BridgeMessenger는 상속자들이 메시지를 송수신할 수 있게 한다. Spoke/Hub 스마트 계약은 메시지가 어떻게 전달되거나 수신되는지에 대한 내부 세부 사항을 알 필요 없이 사용에만 집중할 수 있다.


BridgeRouter


BridgeRouter는 사용 가능한 GMP(현재는 Wormhole과 Chainlink CCIP) 간의 차이점을 추상화한다. 이 계약은 GMP의 매핑을 포함하며, 새로운 GMP를 추가하고 기존 GMP를 제거하는 것을 지원한다.


BridgeRouter의 책임은 다음과 같다:


  1. 요청된 메시지를 지정된 GMP를 통해 보낸다.
  2. 수신된 메시지를 원하는 스마트 계약으로 전달한다.
  3. 실패한 메시지를 저장하여 재시도/역전시킨다.

 


IBridgeAdapter


인터페이스 IBridgeAdapter는 BridgeRouter에서 호출할 수 있도록 필요한 GMP의 구현을 지정한다. 각 GMP는 자신의 필요에 맞는 어댑터를 갖게 되며, 데이터만 보내거나 데이터와 토큰을 함께 보내는 등의 다양한 기능을 지원하기 위해 여러 어댑터를 가질 수 있다.


WormholeAdapter


WormholeAdapter는 IBridgeAdapter와 IWormholeReceiver를 구현한다. 이 어댑터는 BridgeRouter에서 보내는 메시지를 받아 Wormhole Automatic Relayer를 사용해 전송한다. 또한 다른 체인에서 보내진 메시지를 수신하고, 데이터를 BridgeRouter의 공통 형식으로 변환한다.


CCIPAdapter


CCIPAdapter는 IBridgeAdapter를 구현하고 CCIPReceiver로부터 상속받는다. 이 어댑터는 BridgeRouter에서 보내는 메시지를 받아 Chainlink Relayer를 사용해 전송한다. 또한 다른 체인에서 보내진 메시지를 수신하고, 데이터를 BridgeRouter의 공통 형식으로 변환한다.


Sending a Message


메시지를 보내기 위해서는 BridgeMessenger를 구현하고 sendMessage 함수를 호출해야 한다. 이 함수는 다음과 같은 매개변수를 가진다:


params MessageParams 메시지를 어떻게 보내고 어떤 설정을 사용할지에 관한 매개변수.
sender bytes32 메시지를 보낸 출처 주소. 일반적으로 Spoke나 Hub 계약 주소일 것이다.
destinationChainId uint64 목적지 체인의 식별자. 각 GMP는 자체 사전(dictionary)에서 chainIds를 가지고 있으며, BridgeRouter는 이러한 chainIds와 각 GMP의 chainIds 간의 매핑을 정의한다.
handler address 목적지 체인에서 수신된 메시지를 처리할 주소. 수신자와는 다른 주소다.
payload bytes 전송할 데이터. 호출된 작업에 따라 다르다.
finalityLevel uint8 메시지가 목적지 체인에서 수신되기 전에 기다려야 하는 최종성 수준. 해당 기능을 지원하지 않는 어댑터를 사용하는 경우 무시된다.
extraArgs bytes 업그레이드를 위해 설정 가능한 매개변수. 토큰 전송 시 값이 설정된다.

 


메시지 전송 예시


메시지가 내부적으로 어떻게 전송되는지 이해하기 위해 메시지 lifecycle 예시를 살펴보자.


Invite Address 작업 예시



사용자가 Spoke 체인에서 "Invite Address" 거래를 제출하고, CCIP를 사용하여 크로스체인 통신을 위한 의도를 전달한다고 가정하자.


"Invite Address" 작업의 구현은 Hub 체인으로 메시지를 보내야 한다. 따라서 Spoke는 다음 매개변수로 BridgeRouter를 호출한다:


 

params MessageParams - adapterId: uint16: CCIP 어댑터 식별자
- returnAdapterId: uint16: 0
- receiverValue: uint256: 0
- gasLimit: uint256: 지정된 가스 한도
- returnGasLimit: uint256: 0
sender bytes32 Spoke 스마트 계약 주소
destinationChainId uint64 Hub 체인 식별자
handler bytes Hub 스마트 계약 주소
payload bytes 사용자가 자신의 계정에 X 주소를 추가하는 요청을 인코딩한 데이터
finalityLevel uint8 CCIP가 이 기능을 지원하지 않기 때문에 무시됨
extraArgs bytes 빈 값

동작 과정


  1. BridgeRouter: 요청을 수신하고 원본 발신자가 유효한지 확인한다. 그 후, 메시지를 CCIPAdapter로 전달한다.
  2. CCIPAdapter: 전달받은 메시지를 수신하고 발신자가 BridgeRouter임을 확인한다. 그런 다음, 메시지를 CCIP에 필요한 형식으로 변환하여 Hub 체인의 해당 CCIP 어댑터에 메시지를 보낸다.

 


Receiving a Message


메시지를 수신하기 위해서는 BridgeMessenger를 구현하고 _receiveMessage 함수를 호출해야 한다. 이 함수는 수신된 메시지를 처리하는 로직을 인코딩하며, 다음과 같은 매개변수를 가진다:


messageId bytes32 어댑터와 결합된 메시지의 고유 식별자.
sourceChainId uint16 메시지가 전송된 체인.
sourceAddress bytes32 메시지가 전송된 주소.
handler bytes32 메시지를 처리할 스마트 계약의 주소. 컨텍스트 스마트 계약 주소와 동일해야 한다.
payload bytes 수신된 데이터. 호출된 작업에 따라 다르다.
returnAdapterId uint16 적용 가능한 경우, 반환 메시지를 라우팅할 어댑터.
returnGasLimit uint256 적용 가능한 경우, 반환 메시지의 가스 한도.

메시지 수신 예시


메시지가 내부적으로 어떻게 수신되는지 이해하기 위해 메시지 lifecycle 예시를 살펴보자.



Invite Address 작업 예시


Spoke 체인에서 전송된 "Invite Address" 거래는 Hub 체인에서 수신된다. Chainlink Relayer가 CCIPAdapter의 ccipReceive(...) 함수를 호출하여 메시지 발신자가 유효한지 확인한다. 발신자는 해당 Spoke 체인의 어댑터에서 오는 경우 유효한 것으로 간주된다. 어댑터는 메시지를 BridgeRouter에 필요한 공통 형식으로 변환한 후 receiveMessage(...) 함수를 호출한다.


messageId bytes32 어댑터와 결합된 메시지의 고유 식별자.
sourceChainId uint16 메시지가 전송된 체인.
sourceAddress bytes32 메시지가 전송된 주소.
handler bytes32 메시지를 처리할 스마트 계약의 주소. 컨텍스트 스마트 계약 주소와 동일해야 한다.
payload bytes 수신된 데이터. 호출된 작업에 따라 다르다.
returnAdapterId uint16 적용 가능한 경우, 반환 메시지를 라우팅할 어댑터.
returnGasLimit uint256 적용 가능한 경우, 반환 메시지의 가스 한도.

동작 과정


  1. BridgeRouter: 수신된 메시지가 알려진 어댑터에서 온 것인지 확인한다. 전달하기 전에 메시지 식별자와 메시지를 매핑에 저장한다. 이는 두 가지 목적을 가진다:
    • 동일한 메시지가 두 번 수신되지 않도록 보장한다.
    • 실패한 메시지를 나중에 다시 시도하거나 복구하기 위해 기록한다.
  2. BridgeMessenger: BridgeRouter의 호출을 확인하고, _receiveMessage 함수를 내부적으로 호출한다. 이 함수는 메시지를 적절하게 처리할 수 있도록 자유롭게 구현된다.

Round Trip Messages


GMPs(Generic Messaging Protocols)는 메시지를 보낼 때 수수료를 청구한다. 이 수수료에는 다음과 같은 고려 사항이 포함된다:


  • 메시지를 보내는 기본 수수료.
  • 자동 릴레이어를 사용하는 경우, 메시지 수신 시 가스 비용에 대한 수수료.
  • 자동 릴레이어를 사용하고 명시된 경우, 메시지 수신 시 가치 부착에 대한 수수료. 수수료는 일반적으로 소스 체인의 네이티브 가스 토큰으로 지불된다.

 


일부 작업은 메시지의 왕복이 필요하다. 다음은 그 과정이다:


  1. Spoke 체인에서 작업이 시작된다.
  2. Spoke 체인에서 Hub 체인으로 메시지가 전송된다.
  3. Hub 체인에서 메시지가 수신된다.
  4. Hub 체인에서 Spoke 체인으로 메시지가 다시 전송된다.
  5. Spoke 체인에서 메시지가 수신된다.

 


단계 4에서 발생하는 수수료는 어떻게 지불할 것인?


답은 단계 2에서 더 높은 수수료를 지불하여 단계 3에서 가치가 첨부되도록 하는 것이다. 이 메커니즘은 Wormhole에서 지원되지만 CCIP에서는 지원되지 않는다.


각 사용자는 Hub 체인에 메시지를 Spoke 체인으로 다시 보낼 수 있는 잔액을 가지게 된다. Spoke 체인에서 작업을 시작할 때, 사용자는 Hub 체인의 잔액을 늘리기 위해 추가 수수료를 지불할 옵션이 있다. 대안으로, 기존 잔액을 사용하거나 추가 수수료를 일부 또는 전혀 지불하지 않고 둘을 조합해서 사용할 수 있다.


또한, 사용자가 필요한 수수료를 과대 추정할 경우 추가 가치를 저장하여 나중에 사용하거나 인출할 수 있도록 Spoke 체인에 사용자 잔액을 정의한다.


Messaging Schema


메시지를 수신할 때, GMP는 메시지 식별자, 소스 체인 및 메시지의 소스 주소와 같은 몇 가지 공통 매개변수를 제공한다. 이러한 정보는 이미 포함되어 있으므로 메시징 스키마에 명시적으로 통합하지 않는다.

메시지의 첫 두 바이트는 메시지와 관련된 작업 유형을 인코딩한다. 다음 32 바이트는 메시지와 관련된 계정 ID를 나타낸다. 그 다음 32 바이트는 메시지와 관련된 사용자 주소를 나타낸다. 마지막으로, 다음 언타임드(untyped) 바이트 집합은 작업에 특정한 세부 사항을 인코딩하는 데 사용된다.



USDC Transfers


"To Bridge or Not To Bridge" 섹션에서 토큰 전송에 대한 하이브리드 접근 방식에 대해 설명했다. USDC와 기타 크로스체인 네이티브 토큰은 Hub 체인으로 브리지되고, 다른 모든 토큰은 각각의 Spoke 체인에 남아 있게 된다.


Circle CCTP를 사용하여 크로스체인 네이티브 USDC 전송을 지원한다. Circle CCTP의 depositForBurn 함수는 소스 체인에서 USDC를 소각한 후 목적지 체인에서 USDC를 발행한다. 발행된 USDC는 mintRecipient로 전송된다. depositForBurnWithCaller는 destinationCaller 주소만 목적지 체인에서 USDC를 발행할 수 있도록 제한한다.


프로토콜에서 USDC 전송은 프로토콜의 작업과 결합된다. 예를 들어, USDC 예치 작업이 있다. 따라서 우리는 작업을 인코딩한 메시지 수신과 USDC 발행의 원자적 실행을 원한다. 이것이 depositForBurnWithCaller 함수를 사용하여 destinationCaller를 GMP 메시지 핸들러와 동일한 주소로 지정하는 이유다.


Chainlink CCIP는 USDC 전송을 내부적으로 자동 처리하므로 다른 토큰 전송과 마찬가지로 네이티브 지원을 사용할 수 있다.


GMP 메시지와 CCTP 메시지를 모두 수신하므로, 메시지를 중계하기 전에 둘 모두에 대해 최종성을 기다려야 한다 (CCTP에 대한 증명).


Adapters


어댑터는 특정 GMP에서 메시지를 송수신하는 역할을 한다. 각 GMP를 지원하는 어댑터가 있으며, 이는 메시지의 소스 체인과 목적지 체인 모두에 배포되어야 한다.


GMP는 특정 작업이나 기능을 지원하기 위해 여러 어댑터를 가질 수 있다. 예를 들어, 사용자가 Wormhole 메시지를 Circle CCTP 전송과 결합하여 중계하려는 경우 해당 작업을 구현하는 특정 어댑터를 호출할 수 있다.


어댑터는 메시지가 알려진 소스 체인과 주소에서 온 것인지 확인하고, 형식이 올바른지 확인한 다음 메시지를 BridgeRouter로 전달한다.


CCIP Adapter


  • CCIPAdapter 계약은 CCIPReceiver로부터 상속받으며, _ccipReceive(...) 함수를 오버라이드한다.
  • 이 함수는 Client.Any2EVMMessage 형식으로 메시지를 수신한 후, BridgeRouter에 필요한 공통 형식으로 변환한다.

 


Wormhole Adapter


  • WormholeAdapter 계약은 IWormholeReceiver를 구현하고, receiveWormholeMessages(...) 함수를 구현한다.
  • 이 함수는 바이트 형식으로 메시지를 수신한 후, BridgeRouter에 필요한 공통 형식으로 변환한다.
  • 또한, WormholeAdapter는 기다려야 하는 최종성 수준을 지정할 수 있는 기능이 추가되었다.
  • "USDC Transfers" 섹션에서 설명한 바와 같이 CCTP 원자적 전송을 지원하기 위해 두 번째 WormholeAdapter 버전도 제공된다.

 


Hub Adapter


  • HubAdapter는 메시지 송수신 동작을 모방한다. 메시지의 소스 및 목적지 체인이 모두 Hub 체인을 가리킬 때 사용된다.
  • HubAdapter의 목적은 작업이 어디에서 오는지에 관계없이 Hub Contracts와의 상호작용이 동일하게 유지되도록 하는 것이다.

 


Rate Limit on Cross-Chain Value Transferred


위험을 최소화하고 잠재적인 손실을 제한하기 위해 일정 기간 내에 프로토콜에서 인출할 수 있는 최대 금액에 대해 한도를 설정한다. RateLimited 스마트 계약이 각 Spoke 체인에 배포되어 지원되는 각 토큰에 대한 한도를 설정한다.


매개변수 정의


period uint32 현재 기간 번호. 예를 들어, 기간 길이가 하루로 정의된 경우 각 새로운 날은 기간 번호를 1씩 증가시킨다.
limit uint128 기간당 소비할 수 있는 최대 값.
capacity uint128 새 기간마다 한도로 재설정되는 실제 용량.

Rate Limiting Mechanism



예를 들어, 다음은 하루 동안의 기간과 20 ETH의 최대 한도를 나타낸다:


  • Repay: 5 ETH 반환 -> 용량: 25 ETH
  • Borrow: 20 ETH 대출 -> 용량: 5 ETH (추가 대출 불가)
  • Withdraw: 10 ETH 인출 -> 용량: 10 ETH (이후 대출 가능)

고려 사항


  • 서비스 거부 공격 방지: 공격자가 새로운 기간이 시작되기 전에 악의적으로 예치하고, 새로운 기간이 시작되면 즉시 인출하여 서비스를 거부할 수 있다. 이를 방지하기 위해 한도를 충분히 높게 설정하고, "핫 월렛"을 통해 현재 기간 동안 용량을 일시적으로 증대시킬 수 있다.
  • 토큰 가격 인식 부족: RateLimiter는 토큰 가격을 인식하지 못하므로, 주기적으로 각 버킷의 매개변수를 조정하여 사용성과 보안 간의 적절한 균형을 맞춘다.

 


Account Management


사용자의 Folks Finance 계정을 정의하며, 이 계정은 사용자의 자산을 바탕으로 모든 정보를 포함하고 대출 기능을 가능하게 한다. 계정은 블록체인 주소를 사용하여 관리된다. 각 계정에는 고유한 bytes32 식별자가 있다.


크로스체인 계정 관리


크로스체인 환경에서 계정을 관리하는 것은 어떻게 하는가? 사용자는 여러 블록체인을 사용하여 자신의 계정을 관리하기를 원한다.


이는 주소를 (체인 식별자, 체인 주소) 튜플을 사용하여 식별한다. 이 두 속성의 조합은 중복 없이 단일 엔터티를 고유하게 식별한다. 모든 주소 형식을 지원하기 위해, 우리는 address 대신 bytes32 형식을 사용한다.


사용자 경험을 단순화하기 위해, 계정 당 Spoke 체인에 최대 하나의 주소만 등록할 수 있도록 제한한다. 따라서 사용자가 예를 들어 ETH를 대출하는 경우, 사용자에게 등록된 Ethereum 주소로 ETH를 받을 것이라고 가정할 수 있다.


또한 (체인 식별자, 체인 주소) 쌍을 여러 계정에 등록할 수 없도록 제한한다. 이는 사용자가 다른 계정에 있다는 것을 인식하지 못한 채 프로토콜과 상호작용하는 상황을 방지하기 위함이다. 하나의 주소 당 하나의 계정은 사용자가 제어하는 주소를 자신의 계정에 추가하여 동일한 계정을 자신에게 사용하지 못하게 하는 서비스 거부 공격 가능성을 없앤다. 따라서 초대와 수락 개념을 사용한다. 기존 계정의 등록된 주소는 다른 Spoke 체인의 주소를 초대할 수 있다. 초대받은 주소는 계정에 등록되기 전에 초대를 수락해야 한다.



다이어그램 설명


  • T1: Chain B에서 주소 0x3Da가 주소 0xJT5를 초대한다.
  • T2: Chain A에서 주소 0xJT5가 초대를 수락한다.
  • Hub Chain: Bob의 계정에는 초대된 주소(Chain B의 0x3Da)와 수락된 주소(Chain A의 0xJT5)가 포함된다.

 


Delegatable


크로스체인 대출 프로토콜 위에 다른 프로토콜이 구축될 수 있도록, 계정을 관리할 수 있는 또 다른 주소 집합을 활성화한다. 이를 "delegated address"라고 한다. 이들은 Hub 체인에서만 사용할 수 있으며, 계정은 원하는 만큼 많이 추가할 수 있다. 동일한 주소가 여러 계정에 delegate수도 있다.

이 주소들은 크로스체인 대출 프로토콜을 내부적으로 사용하는 Hub 체인의 다른 스마트 계약이 될 것이다.

 

Folks Finance사의 개발문서를 보고 Cross-Chain Lending Protocol이 어떻게 이루어졌고 구상되었는지 알아보기 위해 읽고 정리해보았다.


Cross-Chain Lending Protocol을 구축하는 이유


  • 대출 프로토콜(lending protocol)에서 가장 중요한 요인은 유동성이다. 안정적인 코인이 예치되어 있다면 사용자들은 필연적으로 대출하게 된다. 더 많은 블록체인에 연결함으로써 안정적인 코인을 공급할 수 있는 기회와 용이성이 증가하게 된다.
  • 사용자들은 편리함을 선호한다. 사용자는 자신이 선호하는 지갑과 익숙한 블록체인을 통해 프로토콜과 상호작용하기를 원한다. 따라서 Cross-Chain 프로토콜은 사용자들에게 일반적으로 진입 장벽이 낮다.

 

여기서 주의해야 할 점은 Cross-Chain과 Multi-Chain의 차이이다. "Multi-Chain"은 동일한 프로토콜을 여러 블록체인에 배포하는 것을 의미하는 반면, "Cross-Chain"은 단일 프로토콜을 여러 블록체인에 배포하는 것을 의미한다.


"Multi-Chain" 프로토콜은 사용자가 배포된 블록체인에 따라 원하는 특정 인스턴스를 선택할 수 있게 함으로써 편리성 문제를 해결한다. 그러나 유동성 문제를 해결하지는 못한다. Multi-Chain에서는 서로 고립된 여러 프로토콜 복사본이 존재하여 한 프로토콜에 예치된 자산을 다른 프로토콜에서 대출할 수 없다. 반면에 Cross-Chain은 어떤 체인에 있든 상관없이 모든 유동성을 사용할 수 있어서 Multi-Chain보다 자본 효율성이 훨씬 높다.


특히 대출 프로토콜의 경우, Cross-Chain의 또 다른 이점은 사용자가 한 체인에 예치된 자금을 담보로 다른 체인의 자금을 대출할 수 있다는 점이다. 만약 Cross-Chain 프로토콜이 없다면, 사용자는 자신이 담보로 사용할 자산을 대출하고자 하는 블록체인으로 이동(브릿지)시켜야 한다. 이 과정은 여러 단계를 거쳐야 하는 복잡하고 번거로운 과정이다. 예를 들어, Ethereum 블록체인에 ETH를 예치했지만, Binance Smart Chain(BSC)에서 대출을 받고자 한다면, 사용자는 ETH를 BSC로 브릿지해야 한다. 이 과정은 시간이 걸리고 수수료가 발생할 수 있다. 또한 브리지된 자산은 종종 래핑(wrapped)되어 사용된다(예: ETH → BSC ⇒ wrapped ETH(wETH)). 사용자는 해당 블록체인에서 wETH를 담보로 받아들이는 대출 프로토콜을 찾아야 한다. 모든 대출 프로토콜이 래핑된 자산을 담보로 받아들이는 것은 아니므로, 이 과정은 복잡하고 제한적일 수 있다. 일부 블록체인에서는 특정 자산의 브리지나 래핑이 불가능할 수 있다. 따라서 사용자는 원래의 자산을 다른 체인에서 사용할 수 없게 되어 대출 자체가 불가능해질 수 있다. Cross-Chain 프로토콜은 이러한 문제를 해결한다. 사용자는 자신이 예치한 자산을 어느 블록체인에서든지 담보로 사용할 수 있기 때문에 브리지나 래핑 과정이 필요 없고, 자산을 쉽게 대출할 수 있다.


Hub & Spoke Architecture


Hub & Spoke 모델은 주변 블록체인을 중앙 Hub 블록체인에 연결하는 일련의 Spokes가 있다.



아키텍처 개요


  • Hub 체인과 Spoke 체인 간의 통신: Hub 체인과 Spoke 체인은 Generic Messaging Protocol(GMP)을 사용하여 상호 통신한다. 이 경우, 사용될 GMP는 Wormhole과 Chainlink CCIP이다.
  • 프로토콜 상태 저장: 프로토콜의 전체 상태는 Hub 체인에 저장된다. 대출 기능과 관련된 상태는 Spoke 체인에 저장되지 않으며, Spoke 체인에는 Hub 체인과의 통신 방법과 관련된 상태만 저장된다. 이러한 결정의 주요 이유는 체인 간 상태 비동기성과 경합 조건을 피하기 위해서다.

 

To Bridge or Not To Bridge


위에서 프로토콜 상태가 오직 Hub 체인에만 존재한다는 것을 확인했다. 그러나 프로토콜 토큰이 Hub 체인으로 브리지될 것인지 아니면 각각의 Spoke 체인에 남아 있을 것인지에 대한 추가적인 질문이 있다.


브리징의 장단점을 모두 고려하여 하이브리드 접근 방식을 채택했다. 일부 토큰은 Hub 체인으로 브리지되고, 일부 토큰은 각각의 Spoke 체인에 남아 있을 것이다.


하이브리드 접근 방식


  • Hub 체인으로 브리지되는 토큰:
    • USDC와 기타 크로스체인 네이티브 토큰은 Hub 체인으로 브리지된다.
    • 브리지 선택은 토큰에 따라 다르며, 토큰의 래핑된 버전이 생성되지 않는 브리지를 사용할 것이다.
    • 예를 들어, USDC의 크로스체인 네이티브 전송을 원활하게 하기 위해 Circle CCTP를 사용할 것이다.
    • 사용자는 지원되는 Spoke 체인 중 어느 체인에서나 이러한 토큰을 예치하고 대출할 수 있다(토큰과 브리지 쌍이 원하는 블록체인을 지원하는 경우).
  • Spoke 체인에 남아 있는 토큰:
    • AVAX와 같은 Spoke 가스 토큰은 각각의 Spoke 체인에 남아 있을 것이다.
    • 사용자는 이 토큰을 해당 Spoke 체인에서만 예치하고 대출할 수 있다.
    • 사용자가 이러한 토큰을 다른 Spoke 체인에서 받고자 한다면, 다른 Spoke 체인에서 받은 후에 추가적인 브리징 단계를 거쳐야 한다.
     

 


USDC

  • USDC 다이어그램: Chain A에서 예치된 USDC는 Hub 체인으로 전송되고, Chain B에서 대출된 USDC는 Hub 체인에서 출금되어 전달된다. Hub 체인은 예치와 대출 상태를 관리하며, 유동성을 유지한다.

 


ETH

  • ETH 다이어그램: Ethereum 체인에서 전송된 ETH는 Hub 체인으로 예치되고, Chain A에서 대출된 ETH는 Hub 체인에서 출금되어 전달된다. Hub 체인은 예치와 대출 상태를 관리하며, 유동성을 유지한다.

 

요약

  • 프로토콜 상태: 오직 Hub 체인에만 저장.
  • 토큰 브리징 전략:
    • 브리지되는 토큰: USDC 등, 크로스체인 네이티브 토큰 (예: Circle CCTP 사용).
    • 브리지되지 않는 토큰: AVAX 등, Spoke 체인의 가스 토큰.
  • 사용자 지침:
    • 브리지된 토큰은 여러 Spoke 체인에서 예치 및 대출 가능.
    • Spoke 체인의 가스 토큰은 해당 체인에서만 예치 및 대출 가능.
    • 다른 Spoke 체인에서 가스 토큰을 받고자 할 경우, 추가적인 브리징 필요.

 

Cross-Chain Communication and Finality


블록체인 네트워크에서 포크와 재구성


어떤 블록체인은 다음 Block Proposer가 누구인지 정의하지 않으며, 정의하는 경우에도 Proposer가 오프라인일 때 어떻게 복구할지 정의해야 한다. 이러한 상황은 네트워크가 포크(2개 이상의 유효한 블록이 존재하는 상황)될 수 있으며, 네트워크는 어떤 포크를 기준으로 체인을 구축할지 합의해야 한다.


포크는 네트워크의 다른 부분이 메인 체인에 대해 합의하지 못할 때 여러 블록 동안 지속될 수 있다. 결국 포크는 블록체인의 규칙에 따라 해결되지만, 버려진 블록에 포함된 모든 거래는 제거된다. 이 과정을 '재구성(re-org)'이라고 한다.



재구성 방지와 이중 지출 공격


재구성은 예상할 수 있지만, 재구성을 이용한 공격( Ex) Double Spend Attack : 이중 지출 공격 )을 방지하기 위해 조치를 취해야 한다. 예를 들어, 사용자가 Ethereum에서 Avalanche로 USDC를 브리지한다고 가정하자. 사용자는 Ethereum에서 USDC를 소각하는 거래를 제출하고, 브리지는 이 거래가 Ethereum 체인에 추가로 Avalanche에서 USDC를 발행한다. 그런데 Ethereum에서 재구성이 발생하여 소각 거래가 체인에서 제거되면, 공격자는 USDC를 Ethereum과 Avalanche에서 모두 사용할 수 있게 된다.


이러한 상황을 회피하기 위해서는 최종성(Finality) 개념을 고려해야 한다. 최종성은 거래가 최정적으로 간주되어 재구성이 불가능하거나 확률적으로 매우 희박한 상태를 의미한다.


사용자가 Spoke Chain에 자금을 예치하면 Hub Chain에 있는 Folks Finance 계정에 크레딧이 발생한다. 재구성이 발생하여 사용자의 자금이 스마트 계약에 도달하지 않은 경우, 사용자는 일종의 이중 지출 공격을 성공적으로 수행한 것이다. 이 예시에서 이중 지출 공격은 토큰 브리지뿐만 아니라 더 넓은 의미에서 ‘value’(자산, 화폐 등)를 브리지하는 것에도 적용될 수 있다. Spoke 체인에서는 예치와 상환 작업에 대해 최종성을 가져야 하며, Hub 체인에서는 인출과 대출 작업에 대해 최종성을 가져야 한다.



  • T1 시점:
    • Chain A (Spoke Chain)에서 예치가 발생하지만, 해당 블록이 아직 최종화되지 않음.
    • 따라서, 예치 메시지가 Hub 체인으로 전달되지 않음.
  • T2 시점:
    • Chain A에서 예치가 발생한 블록이 최종화됨.
    • 예치 메시지가 Hub 체인으로 전달되어 최종적으로 처리됨.

 


 

 

최종성을 고려하지 않아도 되는 경우


일부 작업, 특히 "가치"(예: 자산, 화폐)가 전송되지 않는 경우에는 최종성 여부가 중요하지 않다. 이러한 경우에는 사용자 경험을 향상시키기 위해 메시지를 즉시 중계할 수 있다. 네트워크에서 원래 거래가 제거되더라도 다시 제출할 필요가 없다.


예를 들어, 프로토콜에서 "Invite Address"라는 작업이 있다. 이 작업의 목적은 Folks Finance 대출 계정에 다른 체인의 주소를 추가하는 것이다. 추가된 주소는 기존 주소처럼 계정을 관리할 수 있다. (예: 인출, 다른 주소 초대 등)


사용자가 Spoke 체인에서 "Invite Address" 거래를 제출했다고 가정하자. 우리는 최종성을 기다리지 않고 메시지를 즉시 Hub 체인으로 중계한다. Hub 체인은 새로운 정보를 가지고 상태를 업데이트한다. 이후 Spoke 체인이 재구성(re-org)되어 원래 거래가 네트워크에서 제거된다 하더라도, 이 사실은 프로토콜에 아무런 영향을 미치지 않는다. 원래 거래의 목적은 사용자가 새로운 주소를 계정에 추가하고자 하는 의도를 나타내는 것이기 때문에, 이 거래에는 "가치"가 포함되지 않아 이중 지출 공격의 위험이 없다.



이 원칙은 프로토콜의 많은 작업에 적용된다. 사실, 인출 및 대출 작업의 일부에서도 최종성을 기다릴 필요가 없다. 사용자는 Spoke Chain A에서 ETH를 대출하고자 하는 요청을 제출할 수 있다.



사용자가 1 ETH를 대출하려는 요청을 나타내는 거래에 대해서는 최종성을 기다리지 않는다. 다시 말해, 이러한 거래는 사용자의 의도를 정의하는 데만 사용되기 때문이다. 그러나 Hub 체인에서 Spoke 체인으로 전송되는 메시지의 경우에는 최종성을 기다려야 한다. 이는 해당 메시지가 가치 전송을 인코딩하기 때문이다. Hub 체인은 사용자가 추가로 1 ETH를 대출받았음을 나타내도록 상태를 업데이트한다. 만약 사용자가 1 ETH를 받은 후에 이 거래가 Hub 체인에서 제거된다면, 사용자는 동일한 예치금을 사용해 더 많은 자금을 대출하거나 전액 인출할 수 있는 형태의 이중 지출 공격을 성공적으로 수행하게 된다.


현재 CCIP는 소스 체인 거래의 최종성 수준을 지정하는 기능을 제공하지 않는다. 따라서 이러한 유형의 거래에 대해서는 기본적으로 Wormhole을 사용할 것이다. 이 기능이 CCIP에 구현될 때까지 말이다.


요약


  • 최종성을 고려하지 않는 경우: "가치"가 전송되지 않는 작업에서는 최종성을 기다리지 않아도 된다.
  • 메시지를 즉시 중계하여 원래 거래가 제거되더라도 다시 제출할 필요가 없다.
  • 예시: Invite Address: Folks Finance 대출 계정에 다른 체인의 주소를 추가하는 작업으로, 최종성을 기다리지 않아도 된다.
  • 사용자가 1 ETH를 대출하려는 요청 거래는 사용자의 의도를 나타내므로 최종성을 기다리지 않는다.
  • 그러나 Hub 체인에서 Spoke 체인으로의 가치 전송 메시지는 최종성을 기다려야 한다.
  • 현재 CCIP는 소스 체인 거래의 최종성 수준을 지정할 수 없으므로 Wormhole을 사용한다.

Introduction


블록체인을 공부하다 보면 '스왑'이라는 말을 많이 들어봤을 것이다. 그중에서도 특히 Uniswap을 많이 접했을 것이다. 나는 이 Uniswap이 무엇이며, 어떤 역할을 하는지, 그리고 Uniswap에서 x * y = k라는 수식이 어떤 역할을 하는지 궁금했다. 지금부터 내가 전에 공부하면서 들었던 질문들을 중심으로 정리해보려고 한다.


Term


이 용어들은 아래 설명을 이해하는 데 도움이 된다. 한 번 읽고 나서 아래 글을 읽는 것을 추천한다.


오더북 (Order Book)


오더북은 거래소에서 매수 및 매도 주문이 나열되는 전자 장부이다. 매수 주문과 매도 주문이 각각 가격과 수량에 따라 정렬되어 있으며, 사용자는 이 오더북을 통해 원하는 가격에 주문을 넣을 수 있다. 매수 주문과 매도 주문이 일치할 때 거래가 성사되며, 호가창을 생각하면 쉽다.


유동성 (Liquidity)


유동성은 자산을 신속하게 매매할 수 있는 능력을 의미한다. 유동성이 높을수록 자산을 쉽게 사고팔 수 있으며, 시장 가격에 큰 영향을 미치지 않는다. 유동성이 낮으면 거래가 어려워지고, 가격 변동성이 커질 수 있다.


유동성 공급자 (Liquidity Provider)


유동성 공급자는 거래소에서 유동성을 제공하는 역할을 하는 참여자이다. 이들은 자신의 자산을 유동성 풀에 예치하여, 거래소에서 다른 사용자가 거래를 원할 때 해당 자산을 이용할 수 있게 한다. 유동성 공급자는 거래 수수료를 통해 보상을 받는다.


풀 (Pool)


풀은 유동성 공급자가 예치한 자산이 모여 있는 곳이다. 유동성 풀에는 다양한 토큰 쌍이 포함될 수 있으며, 거래는 이 풀을 통해 이루어진다. 예를 들어, ETH/USDT 풀에는 이더리움(ETH)과 테더(USDT)가 예치되어 있다.


가스 (Gas)


가스는 이더리움 블록체인 상에서 거래를 수행할 때 지불하는 수수료이다. 가스 수수료는 거래의 복잡성과 네트워크 상태에 따라 달라지며, 가스는 이더(ETH)로 지불된다. 가스는 블록체인 네트워크의 작업을 처리하는 마이너에게 지급된다.


토큰 쌍 (Token Pair)


토큰 쌍은 두 개의 암호화폐 또는 토큰이 거래되는 조합을 의미한다. 예를 들어, BTC/ETH는 비트코인(BTC)과 이더리움(ETH) 간의 거래를 의미한다. 거래소에서는 다양한 토큰 쌍이 존재하며, 사용자는 원하는 토큰 쌍을 선택하여 거래할 수 있다.


마이너 (Miner)


마이너는 블록체인 네트워크에서 새로운 블록을 생성하고, 트랜잭션을 검증하여 블록체인에 추가하는 참여자를 의미한다. 마이너는 컴퓨팅 파워를 사용하여 복잡한 수학적 문제를 해결하는 과정을 통해 보상을 받는다.


  1. 트랜잭션 검증: 네트워크에서 발생하는 트랜잭션을 수집하고, 그 유효성을 검증한다. 이를 통해 이중 지불(double spending) 문제를 방지하고, 트랜잭션의 신뢰성을 확보한다.
  2. 블록 생성: 검증된 트랜잭션을 모아 블록을 생성한다. 새로운 블록을 생성하기 위해 마이너는 작업 증명(Proof of Work)과 같은 합의 알고리즘을 사용하여 특정 수학적 문제를 해결해야 한다. 현재 Ethereum은 PoS로 전환되었다.
  3. 블록체인에 추가: 생성된 블록을 블록체인에 추가한다. 이를 통해 블록체인이 계속해서 확장되고, 네트워크의 거래 기록이 영구적으로 보존된다.
  4. 보상: 마이너는 새로운 블록을 생성하고 트랜잭션을 검증한 대가로 암호화폐 보상을 받는다. 이 보상은 일반적으로 블록 보상(block reward)과 트랜잭션 수수료(transaction fees)로 구성된다.

 

합의 알고리즘


  • 작업 증명(Proof of Work, PoW): 마이너가 복잡한 수학적 문제를 해결하는 과정을 통해 새로운 블록을 생성한다. 이 과정은 많은 컴퓨팅 파워를 필요로 하며, 비트코인과 이더리움(이전 PoW 시기)에서 사용되었다.
  • 지분 증명(Proof of Stake, PoS): 마이너 대신 밸리데이터(Validator)가 자신의 암호화폐를 네트워크에 스테이킹(staking)하고, 이를 통해 블록을 생성하고 검증한다. 이더리움 2.0에서 대표적으로 사용된다.

 

What is AMM?


Uniswap이 무엇을 하는 것이냐? 이것이 어떤 시스템인가? 툴인가? 여러 궁금증이 들 수 있다.


간단히 말하자면, Uniswap은 Ethereum 및 기타 블록체인에서 실행되는 분산형 암호화폐 거래소(DEX)이다. 전통적인 거래소와는 달리, Uniswap은 사용자들이 직접 암호화폐를 거래할 수 있도록 한다.


기존 전통적인 거래소와 Uniswap의 가장 큰 차이점은 바로 AMM(Automated Market Maker)를 도입했다는 점이다. AMM은 기존의 Order Book 기반 거래소와 달리 유동성 풀을 통해 거래를 처리한다.


그렇다면 왜 AMM을 도입하게 되었을까?


기존의 거래소들은 블록체인 상에서 암호화폐를 거래할 수 있게 조성한 시스템은 아니다. 사용자들은 거래소가 제공한 중앙화된 시스템 내에서 Order Book 기반 인터페이스를 통해 거래를 하는 것이다. 이러한 중앙화 거래소는 보안 문제, 불투명성, 지역적 제한 등의 여러 문제를 내포하게 된다.


보다 탈중앙화되고, 투명한 거래 방식을 모색한 결과로 탈중앙화 거래소(DEX)가 등장하게 되었다. DEX는 사용자가 직접 자산을 관리하고, 스마트 계약을 통해 거래를 처리하면서 중앙화 거래소의 한계를 극복할 수 있는 대안으로 주목받기 시작했다. 여러 DEX 개발 시도 중 하나가 이더델타(EtherDelta)였다.


이더델타는 기존 거래소(CEX)와 같은 Order Book 기반 거래소를 블록체인 상에서 구현하고자 했다. 결과적으로 이는 크게 성공하지 못했다. 이유를 크게 살펴보자.


  1. Gas Fee 문제: Order Book 방식의 거래는 매수, 매도를 미리 등록하는 방식이기에 거래 생성, 취소 등에 많은 gas fee를 요구했다.
  2. 유동성 부족 문제: 사용자 수가 많지 않았고, 사용자들이 DEX를 사용하는 데 어려움을 겪었기에 이러한 여러 문제들이 곧 유동성 부족 문제로 이어졌다.

 

이러한 문제 때문에 초기 DEX는 성공하지 못했다. 하지만 이러한 문제점을 해결한 것이 바로 Uniswap이었다.

Uniswap은 기존의 Order Book 시스템을 버리고 AMM을 도입하였다. AMM은 Uniswap에서 누구나 유동성을 공급할 수 있었고, 이 유동성을 바탕으로 결정된 가격으로 사용자들이 언제 어디서든 거래할 수 있게 되었다.

이제 AMM에 대해 자세히 살펴보도록 하자.


AMM (feat. CPMM)


Uniswap에서는 여러 AMM 중 CPMM(Constant Product Market Maker)을 사용한다. 용어를 곧이곧대로 해석하면 감이 올 수도 있다.


CPMM은 X * Y = K라는 간단한 식을 기반으로 한다. 여기서 X와 Y는 각각 두 종류의 토큰의 리저브 양을 의미하며, K는 X와 Y의 곱으로 일정하게 유지된다.


이 식의 중요한 점은 K가 항상 일정하게 유지된다는 것이다. 따라서 어떤 한 종류의 토큰이 추가되거나 제거되면, 다른 종류의 토큰의 양이 이에 상응하여 조정된다. 예시를 통해 살펴보자.


XXX와 YYY의 교환


현재 Uniswap의 XXX/YYY 풀에는 30 XXX와 700 YYY가 유동성으로 제공되고 있다. 이 상황에서 트레이더가 1 XXX를 지불하고 YYY를 구매한다고 가정하자. 아래 예시들에서 거래 수수료는 제외하겠다.


  1. 거래 전 상태:
    • XXX 리저브(X): 30
    • YYY 리저브(Y): 700
    • K = X * Y = 21,000
  2. 트레이더가 1 XXX를 지불:
    • 바뀐 XXX 리저브: 31
    • 바뀐 YYY 리저브: Y = 21,000 / 31 ≈ 677.419
  3. 트레이더가 받는 YYY의 양:
    • 700 - 677.419 ≈ 22.581

 

여기서 주목할 점은 거래 전후 XXX와 YYY의 비율이 변화하여 XXX의 가격은 상승하고 YYY의 가격은 하락한다는 점이다. 처음에 의도했던 가격과 실제 거래 가격 사이의 차이를 슬리피지(Slippage)라고 하는데, 이는 CPMM의 특성상 발생할 수밖에 없다.


조금 생각해보면 유동성이 크면 클수록 상대적인 슬리피지 양이 줄어들 수밖에 없다는 것을 알 수 있다. 한 번 살펴보자.

이제 유동성을 증가시킨 상황을 가정해보자.


현재 Uniswap의 XXX/YYY 풀에 30,000 XXX와 700,000 YYY가 유동성으로 제공되고 있다. 이 상황에서 동일하게 트레이더가 1 XXX를 지불하고 YYY를 구매한다고 가정하자.


  1. 거래 전 상태:
    • XXX 리저브(X): 30,000
    • YYY 리저브(Y): 700,000
    • K = X * Y = 21,000,000,000
  2. 트레이더가 1 XXX를 지불:
    • 바뀐 XXX 리저브: 30,001
    • 바뀐 YYY 리저브: Y = 21,000,000,000 / 30,001 ≈ 699,976.667
  3. 트레이더가 받는 YYY의 양:
    • 700,000 - 699,976.667 ≈ 23.333 YYY

 

유동성이 증가함에 따라 슬리피지가 어떻게 변했는지를 살펴보면, 동일한 1 XXX를 거래했을 때 유동성이 적었던 첫 번째 예시에서는 약 22.581 YYY를 받았지만, 유동성을 증가시킨 두 번째 예시에서는 약 23.333 YYY를 받게 되었다. 슬리피지가 줄어들었음을 확인할 수 있다. 그래서 Uniswap을 포함한 DEX는 TVL(Total Value Locked)로 표현되는 많은 유동성을 확보하기 위해 노력한다.


하지만 많은 유동성만이 슬리피지를 해결하는 방법은 아니다. 이는 Uniswap V3에서 도입된 집중화된 유동성(Concentrated Liquidity)을 찾아보길 권한다.


Gas Fee


초기 DEX들이 실패한 원인 중 하나가 Gas Fee였다. 이더리움 블록체인에서 거래를 실행할 때마다 발생하는 가스 수수료는 사용자가 네트워크의 계산 자원을 사용하는 대가로 지불해야 하는 비용이다. 위에서 봤듯이, 초기 DEX들, 예를 들어 이더델타(EtherDelta)는 복잡한 스마트 계약을 사용하여 거래를 처리하였고, 이는 높은 가스 비용을 초래했다. 이러한 높은 가스 수수료는 사용자들이 자주 거래를 취소하거나 변경할 때마다 추가적인 비용을 발생시키며, DEX를 사용하는 데 있어 큰 장애물로 작용했다.


유니스왑은 이러한 문제를 해결하기 위해 가스 수수료를 낮추는 데 주력했다. 유니스왑은 CPMM 모델을 채택하여 거래 과정을 단순화하고, 복잡한 주문서 관리 시스템을 없앴다. 이는 거래를 수행할 때 필요한 계산을 최소화하여 가스 비용을 절감하는 효과를 가져왔다. 유니스왑의 간단하고 효율적인 스마트 계약 구조는 가스 비용을 크게 줄였고, 이를 통해 사용자들은 보다 저렴한 비용으로 거래할 수 있게 되었다.


이러한 효율성 덕분에 유니스왑은 초기부터 많은 사용자를 끌어들일 수 있었고, 이는 유니스왑의 성공에 중요한 역할을 했다. 낮은 가스 수수료는 유동성 공급자와 트레이더 모두에게 유리한 환경을 제공하였고, 이는 유니스왑이 다른 DEX들과 차별화될 수 있는 중요한 요인이 되었다.


결론적으로, 유니스왑의 성공은 가스 수수료를 효율적으로 관리한 덕분이다. 이를 통해 사용자들은 부담 없이 거래할 수 있었고, 유동성 공급자들은 보다 안정적인 수익을 기대할 수 있었다. 이는 유니스왑이 초기에 성공할 수 있었던 주요 원인 중 하나로 작용했다.


Liquidity Provider


Uniswap에서 유동성 공급자(Liquidity Provider, LP)의 역할은 매우 중요하다. 중앙화된 거래소와는 달리, 유니스왑은 유동성 공급자들이 풀에 제공하는 자산을 통해 거래를 처리한다. 이는 유동성 공급자들이 유니스왑의 성공과 안정성에 직접적인 영향을 미친다는 것을 의미한다.


유동성 공급자의 역할


유니스왑에서 유동성 공급자가 되려면, 예를 들어 ETH/DAI 풀에 유동성을 제공한다고 할 때, 사용자는 이더리움(ETH)과 다이(DAI)를 일정 비율로 예치해야 한다. 이렇게 예치된 자산은 유동성 풀을 형성하며, 다른 사용자가 이 풀에서 자유롭게 거래를 할 수 있도록 돕는다. 유동성 풀의 크기가 클수록 거래가 더 원활하게 이루어질 수 있고, 슬리피지(Slippage)도 줄어들게 된다.


LP 토큰의 의미


유동성 공급자들이 자산을 풀에 예치하면, 그 대가로 LP 토큰을 받는다. 이 LP 토큰은 유동성 공급자가 풀에 기여한 비율을 나타낸다. 예를 들어, ETH/DAI 풀에 전체 유동성의 10%를 기여했다면, 유동성 공급자는 전체 LP 토큰의 10%를 받게 된다. 이 LP 토큰은 단순한 기여도를 나타내는 지표에 그치지 않고, 풀에서 발생하는 거래 수수료를 배분받을 권리도 부여한다.


거래 수수료 수익


유니스왑에서 이루어지는 모든 거래는 소액의 거래 수수료를 발생시키며, 이 수수료는 유동성 풀로 귀속된다. 그리고 이 수수료는 유동성 공급자들에게 배분된다. 예를 들어, 특정 풀에서 한 달 동안 100 ETH의 거래 수수료가 발생했다면, 해당 풀에 10%의 기여를 한 유동성 공급자는 10 ETH의 수수료를 받게 된다.


유동성 공급의 장점과 리스크


유동성 공급자는 거래 수수료를 통해 수익을 얻을 수 있지만, 이는 리스크도 수반한다. 가장 큰 리스크 중 하나는 '영구적 손실(Impermanent Loss)'이다. 이는 예치한 자산의 상대적 가치 변화로 인해 발생할 수 있는 손실을 의미한다. 예를 들어, 예치한 두 자산 중 하나의 가격이 급격히 변동하면, 유동성 풀에서의 비율이 달라져 손실이 발생할 수 있다.


Reference Link

https://medium.com/@aiden.p/uniswap-series-1-유니스왑-이해하기-e321446623c7

+ Recent posts