개발 가이드

이 장에서는 플래시론을 활용하여 개발하는 방법 및 동작 방식을 알아보기위해 가장 간단한 형태의 예시와 함께 설명합니다. 아래의 가이드에서 설명의 편의를 위해 플래시론을 활용해 개발할 컨트랙트를 A컨트랙트라 표기하도록 하겠습니다.

A컨트랙트는 클레이 뱅크의 LendingPool 컨트랙트와 상호작용하게 됩니다. 플래시론을 통한 코드의 진행순서는 다음과 같습니다.

1. 플래시론을 활용할 상황이 생겼을 때 개발자가 컨트랙트의 특정 함수를 호출합니다. 여기서는 예시로 exampleFlashLoanCall 을 호출했다고 가정합니다. 즉 A컨트랙트에는 exampleFlashLoanCall 함수가 구현되어 있습니다.

2. exampleFlashLoanCall 함수에서 필요한 자산 과 양을 설정하여 LendingPool 컨트랙트 에 FlashLoan을 요청하는 flashLoan 함수를 호출합니다. 파라미터에 대한 설명은 여기를 참고해주세요.

interface ILendingPool{
    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
      ) external;
} 

3. LendingPool 컨트랙트A컨트랙트가 요청한 금액만큼 자산을 전달합니다.

4. LendingPool 컨트랙트A컨트랙트executeOperation 함수를 호출합니다. executeOperationIFlashLoanReceiver 에 정의되어있으며 플래시론을 활용할 컨트랙트는 이 인터페이스를 반드시 구현해야합니다. 각 파라미터에대한 설명은 여기를 참고해주세요.

interface IFlashLoanReceiver {
    function executeOperation(
      address[] calldata assets,
      uint256[] calldata amounts,
      uint256[] calldata premiums,
      address initiator,
      bytes calldata params
    ) external returns (bool);
}

A컨트랙트가 실제로 자산을 빌려 동작을 수행하는 코드는 executeOperation 함수 내에서 수행되게 됩니다. executeOperation 함수는 내부에서 자산을 활용한 뒤 반드시 상환해야할 양(amounts + premium) 만큼의 자산(토큰) 을 보유하고 있어야 합니다. 또한 LendingPool 컨트랙트가 회수해 갈 수 있도록 상환해야할 양 이상 토큰을 LendingPool 컨트랙트에 approve 해주어야 합니다.

5. LendingPool 컨트랙트 는 마지막으로 amounts + premium 만큼의 토큰을 A컨트랙트 로부터 회수(transferFrom) 해옵니다.

위의 1~5 까지의 동작중에 예외가 발생하거나 특히

  • 4. 에서 A컨트랙트 가 false를 리턴하는경우

  • 5. 에서 LendingPool 컨트랙트가 자산이 회수가 불가능한 경우

모든 실행이 취소됩니다.

따라서 대출자(Borrower)는 담보 없이 자산을 빌릴 수 있고, 대여자(Depositor)는 위험성 없이 수수료를 얻을 수 있습니다.

지금까지의 과정을 도식으로 보면 아래와 같습니다.

파라미터 설명(flashLoan)

아래는 flashLoan 함수를 호출하기위한 파라미터와 이에대한 설명입니다.

interface ILendingPool{
    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
      ) external;
}
  • address receiverAddress : 플래시론을 통해 대출을 발생시켰을 때 자산을 받아가고, executeOperation 가 구현되어있는 주소입니다. 이 예제에서는 A컨트랙트receiverAddress가 됩니다.

  • address[] calldata assets : 대출 받을 자산의 목록입니다.

  • uint256[] calldata amounts : 대출 받을 자산의 양 목록입니다.

  • uint256[] calldata modes : 대출 받을 자산들에 대해 대출 모드를 설정합니다. 현재 클레이뱅크의 대출모드는 0만 지원하며 나머지 타입은 추후 지원 예정입니다. 모드는 다음과 같습니다.

    • 0 : 즉시 상환을 의미합니다. 플래시론을 통해 빌린 자산은 트랜잭션 종료와 함께 반드시 상환되어야 합니다. 그렇지 않은경우 실패합니다.

  • address onBehalfOf : 대출모드가 0이아닐때 사용됩니다. 현재는 값이 무시되므로 msg.sender 를 입력하셔도 무방합니다.

  • bytes calldata params : LendingPool 컨트랙트가 executeOperation 을 통해 사용자 A컨트랙트를 호출할때 넘겨줄 파라미터를 지정할 수 있습니다.

  • uint16 referralCode : 특정상황에 레퍼럴 구분자로 이용하기 위한 파라미터입니다. 일반 사용시에는 0을 입력하면 됩니다.

파라미터 설명(IFlashLoanReceiver)

아래는 executeOperation 함수를 호출하기위한 파라미터와 이에대한 설명입니다.

interface IFlashLoanReceiver {
    function executeOperation(
      address[] calldata assets,
      uint256[] calldata amounts,
      uint256[] calldata premiums,
      address initiator,
      bytes calldata params
    ) external returns (bool);
}
  • address[] calldata assets : 대출한 자산 목록을 의미합니다.

  • uint256[] calldata amounts : 대출한 금액 목록을 의미합니다.

  • uint256[] calldata premiums : 대출한 자산에 대한 수수료가 반영된 금액 목록을 의미합니다. 즉 amount + premium 만큼을 상환해야합니다.

  • address initiator : 플래시론을 호출한 주소를 나타냅니다.

  • bytes calldata params : 플래시론을 호출할때 넘겨준 파라미터 데이터를 나타냅니다

예시 컨트랙트

아래는 지금까지 설명했던 A컨트랙트의 예시입니다.

contract AContract is IFlashLoanReceiver {
// 랜딩풀은 클레이 뱅크의 Lending Pool 주소를 활용해야합니다.
// 또는 클레이 뱅크의 Address Provider를 통해 Lending Pool 주소를 얻어올 수 있습니다.
    ILendingPool public LENDING_POOL; 

    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    )
        external
        override
        returns (bool)
    {
        // 실제 자산을 획득한 뒤 수행할 코드는 여기에 작성하면 됩니다.
        // 파라미터로 넘어온 assets와 amounts를 활용해 코드를 진행합니다.
        // 필요시에 initiator 주소도 활용할 수 있습니다. ( 함수호출자 검증 등 )

        // 특별히 부채생성 모드로 실행하지않은경우 함수 종료시점에 자산을 반환해야합니다.
        // 아래 예시는 클레이뱅크의 Lending Pool 컨트랙트가 자산을 가져갈 수 있도록 토큰에 Approve를 진행해줍니다.
        for (uint i = 0; i < assets.length; i++) {
	    uint amountToRepay = amounts[i].add(premiums[i]);
	    IKIP7(assets[i]).approve(address(LENDING_POOL), amountToRepay);
	}

	/*

	이 부분에 flashloan을 이용해 수행하고자하는 로직을 작성합니다.

	*/ 

	// 로직전개중 트랜잭션을 취소하고싶을때 가스비 최소화를 위해 false를 리턴할 수 있습니다.
	// 일반적인 성공 상황에서는 true를 리턴해주면 됩니다.
	return true;
    }

    function exampleFlashLoanCall() public {
        address receiverAddress = address(this);
        address[] memory assets = new address[](2);
        assets[0] = address(0xcee8faf64bb97a73bb51e115aa89c17ffa8dd167); // KUSDT
        assets[1] = address(0x5c74070fdea071359b86082bd9f9b3deaafbe32b); // KDAI

        uint256[] memory amounts = new uint256[](2);
        amounts[0] = 1000000; // 1 KUSDT
        amounts[1] = 1 ether; // 1 KDAI

        // 0 = 부채 생성 X 즉시 상환 모드
        uint256[] memory modes = new uint256[](2);
        modes[0] = 0;
        modes[1] = 0;

        address onBehalfOf = address(this);
        bytes memory params = "";
        uint16 referralCode = 0;

        LENDING_POOL.flashLoan(
            receiverAddress,
            assets,
            amounts,
            modes,
            onBehalfOf,
            params,
            referralCode
        );
    }
}

더 알아보기

플래시론을 활용할때 클레이뱅크 LedingPool 컨트랙트flashLoan 함수를 호출하고 executeOperation 를 구현하는 것들이 모두 하나의 컨트랙트 (예시에서는 A컨트랙트) 일 필요는 없습니다.

필요에 따라서 일반 계정(EOA)에서 flashLoan 를 호출하고 receiverAddressexecuteOperation 를 구현한 컨트랙트 주소(예시에서는 A컨트랙트)를 기입해도 무방합니다. 또한 또다른 컨트랙트인 B컨트랙트에서 호출할수도 있습니다. 각각 모두 msg.sender가 달라짐을 유의합니다.

위와같이 누구나 flashLoan 을 호출하여 다른 컨트렉트의 executeOperation 을 호출할 수 있으므로 griefing공격을 조심해야합니다. 해당 공격은 다음과 같이 이루어질 수 있습니다.

  • A컨트랙트에 자산(토큰)을 가지고있고 플래시론을 위해 executeOperation() 이 구현되어있는경우

  • B컨트랙트가 flashloan()receiverAddress 파라미터로 A컨트랙트 주소를 넣고 실행할 수 있습니다.

  • 이 경우 A컨트랙트의 executeOperation() 가 실행 될 것이고, 클레이뱅크의 LendingPool은 A컨트랙트로부터 자산 회수를 시도합니다.

  • A컨트랙트에 자산이 있기때문에 자산회수가 성공하게되고 A컨트랙트 코드에 따라 자산손실이 발생할 수 있습니다.

이 공격을 막기위해서 executeOperation 함수에 적절한 방어로직(initiator 화이트리스팅, 자산 전,후 비교 require문 등..)을 충분히 고려하는것을 권장합니다.

활용방안

클레이 뱅크의 플래시론을 활용하여 순간 큰 자산이 필요할 때 기능을 수행할 수 있습니다.

  • 클레이튼 내 DEX내에서의 큰 규모의 차액거래(Arbitrage) 기회가 생겼을때 활용하여 차액을 획득할 수 있으며

  • 클레이뱅크 뿐만아니라 다른 대출 프로토콜들의 청산기회가 생겼을때 없는 자산을 빌려 청산을 진행하고 청산 보너스를 얻을 수 있습니다.

이 외 한 트랜잭션내에 큰 규모의 자산을 빌려 이득을 취할수 상황이 있다면 플래시론을 활용하여 큰 이익을 얻을 수 있습니다.

Last updated