체인의정석

Solidity에서 특정 Contract 타입 사용방법과 Storage를 지정하고 Interface Contract를 작성하는 방법 본문

블록체인/Solidity

Solidity에서 특정 Contract 타입 사용방법과 Storage를 지정하고 Interface Contract를 작성하는 방법

체인의정석 2023. 3. 27. 17:02
728x90
반응형

이번에 컨트렉트를 작성하면서 변수명에서 그냥 address가 아닌  

CToken cTokenAddress

이런방식의 파라미터가 함수에 쓰이는 경우를 보았다.

이런경우 인터페이스를 작성할 때 

Storage 부분을 컨트렉트 형태로 먼저 작성한 후에

contract CTokenStorage {
    /**
     * @dev Guard variable for re-entrancy checks
     */
    bool internal _notEntered;

    /**
     * @notice EIP-20 token name for this token
     */
    string public name;

    /**
     * @notice EIP-20 token symbol for this token
     */
    string public symbol;

    /**
     * @notice EIP-20 token decimals for this token
     */
    uint8 public decimals;

    // Maximum borrow rate that can ever be applied (.0005% / block)
    uint internal constant borrowRateMaxMantissa = 0.0005e16;

    // Maximum fraction of interest that can be set aside for reserves
    uint internal constant reserveFactorMaxMantissa = 1e18;

    /**
     * @notice Administrator for this contract
     */
    address payable public admin;

    /**
     * @notice Pending administrator for this contract
     */
    address payable public pendingAdmin;

    /**
     * @notice Contract which oversees inter-cToken operations
     */
    ComptrollerInterface public comptroller;

    /**
     * @notice Model which tells what the current interest rate should be
     */
    InterestRateModel public interestRateModel;

    // Initial exchange rate used when minting the first CTokens (used when totalSupply = 0)
    uint internal initialExchangeRateMantissa;

    /**
     * @notice Fraction of interest currently set aside for reserves
     */
    uint public reserveFactorMantissa;

    /**
     * @notice Block number that interest was last accrued at
     */
    uint public accrualBlockNumber;

    /**
     * @notice Accumulator of the total earned interest rate since the opening of the market
     */
    uint public borrowIndex;

    /**
     * @notice Total amount of outstanding borrows of the underlying in this market
     */
    uint public totalBorrows;

    /**
     * @notice Total amount of reserves of the underlying held in this market
     */
    uint public totalReserves;

    /**
     * @notice Total number of tokens in circulation
     */
    uint public totalSupply;

    // Official record of token balances for each account
    mapping (address => uint) internal accountTokens;

    // Approved token transfer amounts on behalf of others
    mapping (address => mapping (address => uint)) internal transferAllowances;

    /**
     * @notice Container for borrow balance information
     * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action
     * @member interestIndex Global borrowIndex as of the most recent balance-changing action
     */
    struct BorrowSnapshot {
        uint principal;
        uint interestIndex;
    }

    // Mapping of account addresses to outstanding borrow balances
    mapping(address => BorrowSnapshot) internal accountBorrows;

    /**
     * @notice Share of seized collateral that is added to reserves
     */
    uint public constant protocolSeizeShareMantissa = 2.8e16; //2.8%
}

해당 스토리지를 상속받아서 abstract 컨트렉트를 작성하여 인터페이스로 사용할 수 있다.

abstract contract CTokenInterface is CTokenStorage {
    /**
     * @notice Indicator that this is a CToken contract (for inspection)
     */
    bool public constant isCToken = true;


    /*** Market Events ***/

    /**
     * @notice Event emitted when interest is accrued
     */
    event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);

    /**
     * @notice Event emitted when tokens are minted
     */
    event Mint(address minter, uint mintAmount, uint mintTokens);

    /**
     * @notice Event emitted when tokens are redeemed
     */
    event Redeem(address redeemer, uint redeemAmount, uint redeemTokens);

    /**
     * @notice Event emitted when underlying is borrowed
     */
    event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);

    /**
     * @notice Event emitted when a borrow is repaid
     */
    event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);

    /**
     * @notice Event emitted when a borrow is liquidated
     */
    event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens);


    /*** Admin Events ***/

    /**
     * @notice Event emitted when pendingAdmin is changed
     */
    event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);

    /**
     * @notice Event emitted when pendingAdmin is accepted, which means admin is updated
     */
    event NewAdmin(address oldAdmin, address newAdmin);

    /**
     * @notice Event emitted when comptroller is changed
     */
    event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);

    /**
     * @notice Event emitted when interestRateModel is changed
     */
    event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel);

    /**
     * @notice Event emitted when the reserve factor is changed
     */
    event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);

    /**
     * @notice Event emitted when the reserves are added
     */
    event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);

    /**
     * @notice Event emitted when the reserves are reduced
     */
    event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves);

    /**
     * @notice EIP20 Transfer event
     */
    event Transfer(address indexed from, address indexed to, uint amount);

    /**
     * @notice EIP20 Approval event
     */
    event Approval(address indexed owner, address indexed spender, uint amount);


    /*** User Interface ***/

    function transfer(address dst, uint amount) virtual external returns (bool);
    function transferFrom(address src, address dst, uint amount) virtual external returns (bool);
    function approve(address spender, uint amount) virtual external returns (bool);
    function allowance(address owner, address spender) virtual external view returns (uint);
    function balanceOf(address owner) virtual external view returns (uint);
    function balanceOfUnderlying(address owner) virtual external returns (uint);
    function getAccountSnapshot(address account) virtual external view returns (uint, uint, uint, uint);
    function borrowRatePerBlock() virtual external view returns (uint);
    function supplyRatePerBlock() virtual external view returns (uint);
    function totalBorrowsCurrent() virtual external returns (uint);
    function borrowBalanceCurrent(address account) virtual external returns (uint);
    function borrowBalanceStored(address account) virtual external view returns (uint);
    function exchangeRateCurrent() virtual external returns (uint);
    function exchangeRateStored() virtual external view returns (uint);
    function getCash() virtual external view returns (uint);
    function accrueInterest() virtual external returns (uint);
    function seize(address liquidator, address borrower, uint seizeTokens) virtual external returns (uint);


    /*** Admin Functions ***/

    function _setPendingAdmin(address payable newPendingAdmin) virtual external returns (uint);
    function _acceptAdmin() virtual external returns (uint);
    function _setComptroller(ComptrollerInterface newComptroller) virtual external returns (uint);
    function _setReserveFactor(uint newReserveFactorMantissa) virtual external returns (uint);
    function _reduceReserves(uint reduceAmount) virtual external returns (uint);
    function _setInterestRateModel(InterestRateModel newInterestRateModel) virtual external returns (uint);
}

이런식으로 컨트렉트에서 스토리지 컨트렉트를 먼저 만들고 이에 대한 컨트렉트 이름 자체를

CTokenInterfaces.sol

이와 같이 만들어 주게 되면 마치 인터페이스 컨트렉트와 같은 역할로 사용이 가능하다.

이렇게 abstract 컨트렉트를 모두 인터페이스 컨트렉트 하단에 정의를 하고 나서 이를 상속받아서 컨트렉트를 

/**
 * @title Compound's CToken Contract
 * @notice Abstract base for CTokens
 * @author Compound
 */
abstract contract CToken is CTokenInterface, ExponentialNoError, TokenErrorReporter {

이런식으로 상속받아서 정의하게 된다면 스토리지 부분만 분리해서 컨트렉트로 만듦과 동시에 인터페이스 자체를 입력값의 자료형으로 만들어서 사용하게 되면 조금 더 체계화 된 구조로 컨트렉트 작성이 가능하다.

아래 링크를 보면 해당 컨트렉트를 호출할 때는 그냥 주소를 넣어주면 된다고 되어 있다.

이는 그냥 코딩스타일의 차이인데 컴파운드에서 사용된 인터페이스 정의 방식이고 한번쯤 써볼만한 스타일인것 같다.

https://ethereum.stackexchange.com/questions/114734/calling-a-smart-contract-with-input-parameter-of-type-interface

 

Calling a smart contract with input parameter of type interface

I have a smart contract with a function that gets an interface (solidity code): contract A { function test(MyInterface contractB) public { contractB.doSomething(); } } interface MyInterfa...

ethereum.stackexchange.com

그리고 해당 부분의 컴파운드 컨트렉트 소스 코드의 경우 아래와 같다.

https://github.com/compound-finance/compound-protocol/blob/master/contracts/CTokenInterfaces.sol

 

GitHub - compound-finance/compound-protocol: The Compound On-Chain Protocol

The Compound On-Chain Protocol. Contribute to compound-finance/compound-protocol development by creating an account on GitHub.

github.com

마지막으로 solidity 공식 도큐먼트에서 해당 타입에 대해서 찾아보겠다.

https://docs.soliditylang.org/en/v0.8.17/types.html

 

Types — Solidity 0.8.17 documentation

Types Solidity is a statically typed language, which means that the type of each variable (state and local) needs to be specified. Solidity provides several elementary types which can be combined to form complex types. In addition, types can interact with

docs.soliditylang.org

If you declare a local variable of contract type (MyContract c), you can call functions on that contract. Take care to assign it from somewhere that is the same contract type.

일반 주소와의 차이점은 다음과 같다고 한다. 만약 컨트렉트 타입으로 컨트렉트를 받아올 경우에는 해당 컨트렉트로 부터 함수를 가져와서 바로 사용할수 있게 된다.

The data representation of a contract is identical to that of the address type and this type is also used in the ABI.

그리고 데이터의 타입은 address와 다를바가 없다고 한다.

한마디로 다른 컨트렉트와 상호작용하는 방안이 2가지가 생긴 것이다.

1. 인터페이스를 만들어서 인터페이스에 주소를 넣고 보내버리기

2. 컨트렉트 타입 자체를 받아와서 초기에 생성자를 만들 때 변수로 지정하고 바로 보내버리기

1번은 기존에 사용하던 방식인데 호출할때마다 주소를 지정해주어야 하는 번거로움이 있고

2번은 인터페이스를 모두 만들어서 호출해야하기에 인터페이스를 모두 만들어 주어야 하는 번거로움이 있다.

그리고 해당 방안을 사용할 시 컨트렉트에 대한 정보도 가져와서 활용이 가능하다고 한다.

https://docs.soliditylang.org/en/v0.8.17/units-and-global-variables.html#meta-type

 

Units and Globally Available Variables — Solidity 0.8.17 documentation

» Units and Globally Available Variables Edit on GitHub Units and Globally Available Variables Ether Units A literal number can take a suffix of wei, gwei or ether to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to

docs.soliditylang.org

 

728x90
반응형
Comments