ERC-2266 아토믹 스왑 기반 아메리칸 콜옵션 (Atomic Swap-based American Call Option Contract Standard)
Last Call · 금고·DeFi · 2019-08-17 작성
신뢰할 중개자 없이 두 사람이 토큰을 맞교환하는 아토믹 스왑(Atomic Swap)을 이용해, 프리미엄을 주고받는 아메리칸 콜옵션(American Call Option) 거래를 스마트 컨트랙트로 구현하는 표준입니다.
왜 필요한가
해시 타임락 컨트랙트(HTLC)를 쓰는 아토믹 스왑은 중개자 없이 토큰을 맞바꿀 수 있지만 거래를 시작한 쪽이 몇 시간 동안 교환을 진행할지 말지 마음대로 고를 수 있는 선택권(optionality)이 생깁니다. 시작한 사람은 그 사이 환율 변동을 지켜보다가 자기에게 유리할 때만 거래를 마치고 불리하면 그냥 포기할 수 있습니다. 이는 금융의 아메리칸 콜옵션과 사실상 같은데도 옵션의 대가인 프리미엄을 내지 않는 셈이라, 상대방은 일방적으로 손해를 봅니다. 반대로 이 선택권을 제대로 활용하면 신뢰할 제3자 없이 콜옵션 상품을 만들 수 있습니다. 이 표준은 프리미엄을 컨트랙트에 함께 걸어 두는 방식으로 차익거래 기회를 없애고 어느 쪽도 상대방의 돈을 악의적으로 묶어 두지 못하게 하는 프로토콜의 공통 구현 방법을 정의합니다.
구현해야 하는 것
function setup(bytes32 secretHash, address payable initiator, address tokenA, address tokenB, uint256 initiatorAssetAmount, address payable participant, uint256 participantAssetAmount, uint256 premiumAmount) public payable: 스왑 컨트랙트를 설정합니다. 거래 양측 당사자, 교환할 토큰, 수량, 프리미엄 금액을 지정합니다.function initiate(bytes32 secretHash, uint256 assetRefundTime) public payable: 개시자가 자신이 팔려는 토큰을 채워 넣고 잠근 뒤 계약에 참가합니다.function fillPremium(bytes32 secretHash, uint256 premiumRefundTime) public payable: 개시자가 프리미엄을 채워 넣고 잠급니다.function participate(bytes32 secretHash, uint256 assetRefundTime) public payable: 참여자가 자신이 팔려는 토큰을 채워 넣고 잠근 뒤 계약에 참가합니다.function redeemAsset(bytes32 secret, bytes32 secretHash) public: 해시락의 원본 값인 secret를 제시하고 상대방이 잠근 토큰을 가져옵니다.function refundAsset(bytes32 secretHash) public: 타임락이 만료된 뒤 자신이 잠갔던 토큰을 되찾습니다.function redeemPremium(bytes32 secretHash) public: 참여자가 프리미엄을 가져옵니다. 참여자가 participate를 이미 호출했고 참여자의 토큰이 수령되거나 환급된 경우에만 호출할 수 있습니다.function refundPremium(bytes32 secretHash) public: 타임락이 만료된 뒤 개시자가 프리미엄을 되찾습니다.event SetUp(bytes32 secretHash, address initiator, address participant, address tokenA, address tokenB, uint256 initiatorAssetAmount, uint256 participantAssetAmount, uint256 premiumAmount);: 한쪽 당사자가 setup()으로 계약을 설정했음을 알립니다.event Initiated(uint256 initiateTimestamp, bytes32 secretHash, address initiator, address participant, address initiatorAssetToken, uint256 initiatorAssetAmount, uint256 initiatorAssetRefundTimestamp);: 개시자가 initiate()로 교환할 토큰을 채우고 잠갔음을 알립니다.event Participated(uint256 participateTimestamp, bytes32 secretHash, address initiator, address participant, address participantAssetToken, uint256 participantAssetAmount, uint256 participantAssetRefundTimestamp);: 참여자가 participate()로 교환할 토큰을 채우고 잠갔음을 알립니다.event PremiumFilled(uint256 fillPremiumTimestamp, bytes32 secretHash, address initiator, address participant, address premiumToken, uint256 premiumAmount, uint256 premiumRefundTimestamp);: 개시자가 fillPremium()으로 프리미엄을 채우고 잠갔음을 알립니다.event InitiatorAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount);: 타임락 만료 전에 secret가 제시되어 개시자의 자산을 상대방이 가져갔음을 알립니다.event ParticipantAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount);: 타임락 만료 전에 secret가 제시되어 참여자의 자산을 상대방이 가져갔음을 알립니다.event InitiatorAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount);: 타임락이 만료되어 개시자가 자신의 자산을 되찾았음을 알립니다.event ParticipantAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount);: 타임락이 만료되어 참여자가 자신의 자산을 되찾았음을 알립니다.event PremiumRedeemed(uint256 redeemTimestamp,bytes32 secretHash,address redeemer,address token,uint256 amount);: 프리미엄을 참여자가 가져갔음을 알립니다. 자산이 secret 제시로 수령됐거나 타임락 만료로 참여자가 환급받았음을 뜻합니다.event PremiumRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address token, uint256 amount);: 참여자가 끝내 참여하지 않아 프리미엄 타임락이 만료되고, 프리미엄이 개시자에게 환급됐음을 알립니다.
심층 설명
용어와 당사자 정의
initiator는 스왑 광고를 올리는 쪽, participant는 그 광고에 동의해 참여하는 쪽입니다. asset은 교환할 토큰 수량, premium은 initiator가 participant에게 옵션의 대가로 지불하는 토큰 수량입니다. redeem은 상대방의 토큰을 가져오는 행위, refund는 타임락 만료 후 자기 토큰을 되찾는 행위입니다. secret는 initiator가 고른 임의 문자열로 해시의 선이미지이고 secretHash는 그 해시값으로 HTLC 구성에 쓰입니다. timelock은 그 시점 전에는 redeem이 가능하고 이후에는 refund만 가능한 기한입니다.
저장 구조: secretHash로 구분되는 네 개의 매핑
컨트랙트는 bytes32 키의 매핑 네 개를 둡니다. swap은 당사자와 토큰 등 스왑 계약의 메타데이터를, initiatorAsset은 개시자가 팔려는 자산의 수량·타임락·상태를, participantAsset은 참여자가 팔려는 자산의 수량·타임락·상태를, premium은 개시자가 걸어 둔 프리미엄의 수량·타임락·상태를 저장합니다. 각 계약은 서로 다른 secretHash를 쓰며 같은 secretHash로 네 매핑이 하나의 계약으로 연결됩니다.
잠금 단계: setup, initiate, fillPremium, participate
setup(secretHash, initiator, tokenA, tokenB, initiatorAssetAmount, participant, participantAssetAmount, premiumAmount)이 계약의 틀을 만듭니다. 이후 개시자는 initiate(secretHash, assetRefundTime)로 자산을, fillPremium(secretHash, premiumRefundTime)으로 프리미엄을 잠그고 참여자는 participate(secretHash, assetRefundTime)로 자신의 자산을 잠급니다. Rationale에 따르면 참여자는 개시자가 토큰을 잠그고 타임락을 설정한 뒤에 참여 여부를 정해야 하고 개시자는 참여자가 잠근 뒤에 스왑 진행 여부를 정합니다.
정산 단계: redeemAsset과 refundAsset
redeemAsset(secret, secretHash)는 해시락의 선이미지인 secret를 제시해 상대방의 토큰을 가져오는 함수입니다. 개시자가 이 함수로 참여자의 토큰을 가져가면 secret가 체인에 공개되므로, 참여자도 같은 secret로 개시자의 토큰을 가져올 수 있습니다. 어느 쪽이든 타임락이 만료되면 refundAsset(secretHash)로 자기가 잠갔던 토큰을 되찾습니다. 수령과 환급은 각각 InitiatorAssetRedeemed, ParticipantAssetRedeemed, InitiatorAssetRefunded, ParticipantAssetRefunded 이벤트로 기록됩니다.
프리미엄 정산 규칙
redeemPremium(secretHash)은 참여자만 쓸 수 있고 참여자가 이미 participate를 호출했으며 참여자의 토큰이 수령되거나 환급된 경우에만 호출할 수 있습니다. 즉 개시자가 옵션을 행사해 secret를 공개했든, 행사하지 않아 참여자가 타임락 만료 후 환급받았든, 일단 참여한 참여자는 프리미엄을 받습니다. 반대로 refundPremium(secretHash)은 개시자가 initiate까지 했지만 참여자가 끝내 참여하지 않은 경우에, 프리미엄 타임락 만료 후 프리미엄을 되찾는 함수입니다. 이 구조가 선택권의 대가를 보장하면서 악의적 자금 묶어두기를 막습니다.
호환성과 구현
이 제안은 완전한 하위 호환을 유지합니다. 기존 표준의 기능을 바꾸지 않고 추가 기능만 제공하므로 기존 토큰 표준에 영향을 주지 않습니다. 원문은 예시 구현(Example.sol)을 함께 제공합니다. 이 문서의 상태는 Last Call이며 마감일은 2020년 12월 31일로 기재되어 있습니다.
쓸 때 주의할 점
- initiateTimestamp가 전체 과정을 덮어야 합니다: 개시자가 자산을 잠글 때 정하는 initiateTimestamp는 스왑 과정 전체를 포괄하도록 잡아야 합니다.
- 프리미엄 입금 전에는 참여하지 않아야 합니다: 참여자는 프리미엄이 예치되기 전에는 절대 participate를 호출하지 않아야 합니다.