[Solidity] Functions

Seokhun Yoon·2022년 2월 16일
0
post-thumbnail

1. Functions

1-1. Input parameters declaration

솔리디티는 입력 매개변수를 선언할 때 타입을 지정하여 선언한다.
매개변수의 이름 앞에는 _를 적어주는게 관습이지만 필수는 아니다.

function process1(int _x, int _y, int _z, bool _flag) {
	// body code
}

anonymous parameters
매개변수 중 사용하지 않는다면 변수명을 쓰지 않고 익명으로 남겨둘 수 있다.

function process2(int _x, int, int, bool _flag) {
  if(_flag) 
  	stateVariable = _x;
}

1-2. Output parameters declaration

여러 출력 매개변수를 튜플(tuple) 데이터 구조로 반환할 수 있다.
returns 키워드 뒤에 출력 매개변수를 지정하고 입력 매개변수와 비슷한 방식으로 선언한다.
함수가 완료되기 전에 모든 출력 매개변수를 올바르게 지정하면 return문을 사용하지 않아도 된다.

function calculate1(int _x, int _y, int _z, bool _flag) 
  returns (int _alpha, int _beta, int _gamma) {
    _alpha = _x + _y;
    _beta = _y + _z;
    if (_flag)
      _gamma = _alpha /_beta;
    else
      _gamma = _z;
}

NOTE
튜플 : 정렬된 요소 목록 (서로 다른 유형으로 이루어져 있음)

필요에 따라서는 return을 사용해서 출력값을 반환할 수도 있다.

function calculate2(int _x, int _y, int _z, bool _flag) 
  returns (int, int, int){
    int _alpha = _x + _y;
    int _beta = _x + _z;
    if (_flag)
      return (_alpha, _beta, _alpha/_beta);
    else
      return (_alpha, _beta, _z);
}

1-3. Function access levels

접근 수준설명
external- 컨트랙트 인터페이스에 나타난다.
- 외부 컨트랙트 또는 client code에서만 호출할 수 있다.
- 컨트랙트 내부에서는 호출할 수 없다.
public- 컨트랙트 인터페이스에 나타난다.
- 컨트랙트의 내부,외부 또는 client code에서 호출 가능하다.
- 함수 접근성 수준의 기본값.
internal- 컨트랙트 인터페이스가 아니다.
- 컨트랙트의 멤버와 상속된 컨트랙트에서만 사용 가능하다.
private- 선언된 컨트랙트 멤버만 호출할 수 있다.
- 상속받은 컨트랙트에서도 호출할 수 없다.

접근 제한자로 선언한 함수 예시

contract SimpleCoin {
  function transfer(address _to, uint _amount) public {}              // public : 내부, 외부 보두 접근 가능
  function checkLimit(uint _amount) private returns(bool) {}          // private : 이 컨트랙트 내에서만 접근 가능
  function validateAccount(address avcount) internal returns(bool) {} // internal : 이 컨트랙트 + 상속받은 컨트랙트에서만 접근 가능
  function freezeAccount(address target, bool freeze) external {}     // external : 외부에서만 접근 가능
}

1-4. Internal function invocation

동일 컨트랙트 내부에서 다른 함수를 직접 접근해서 작동할 수 있다.

contract TaxCalculator {
  function calculateAlpha(int _x, int _y, int _z)
    public returns (int _alpha) {
    _alpha = _x + calculateGamma(_y, _z);	// 같은 컨트랙트 내부의 다른 함수 작동
  }
  function calculateGamma(int _y, int _z)
    internal returns (int _gamma) {			// internal : 이 컨트랙트 + 상속받은 컨트랙트에서만 접근 가능
    _gamma = _y *3 +7*_z;
  }
}

이렇게 함수를 작동하도록 하는 것을 호출 (Call) 이라고 한다.
호출하면 함수 본문은 메모리를 참조하여 매개변수에 직접 접근한다.

1-5. External function invocation

외부 컨트랙트를 참조해서 외부 컨트랙트 함수를 작동할 수 있다.
참조할 때는 해당 컨트랙트의 주소가 필요하다.

contract GammaCalculator {
  function calculateGamma(int _y, int _z)
    external returns (int _gamma) {
    _gamma = _y *3 +7*_z;
  }
}

contract TaxCalculator2 {
  GammaCalculator gammaCalculator;    // gammaCalculator가 GammaCalculator 컨트랙트를 참조한다고 선언
  
  function TaxCalculator(address _gammaCalculatorAddress) {
    gammaCalculator = GammaCalculator(_gammaCalculatorAddress); // gammaCalculator는 _gammaCalculatorAddress에 배포된 인스턴스를 가리킴
  }

  function calculateAlpha(int _x, int _y, int _z)
    public returns (int _alpha) {
      _alpha = _x + gammaCalculator.calculateGamma(_y, _z);
    }
}

위의 코드에서 직접적으로 GammaCalculator 컨트랙트의 주소를 넣지 않은 이유
GammaCalculator 컨트랙트에 버그 혹은 업데이트로 인해 컨트랙트 주소 변경 필요할 경우, 유동적으로 대응이 가능하다.

컨트랙트 내의 public 함수를 호출할 때 this를 사용하면 external 함수를 호출한 것처럼 트랜잭션 메시지를 발생시키고 이를 블록체인에 저장한다.
this를 통해 호출되기 위해서는 무조건 public으로 선언되어야 한다.
아래 코드는 위의 코드와 똑같은 동작을 수행한다.

contract TaxCalculator3 {
  function calculateAlpha(int _x, int _y, int _z) public returns (int _alpha) {
    _alpha = _x + this.calculateGamma(_y, _z); 
    // this 를 통해 calculateGamma 함수를 호출 => external 함수를 호출한 것처럼 동작 : 트랜잭션 메시지 생성 + 블록체인에 저장됨 
  }

  function calculateGamma(int _y, int _z) public returns (int _gamma) {  // this로 호출되려면 public으로 선언되어야 함
    _gamma = _y * 3 + 7 * _z; 
  }
}

1-5. Changing parameters order at function invocation

함수를 호출할 때 이름을 지정해주면 매개변수를 임의 순서로 전달할 수 있다.

contract TaxCalculator4 {
  function calculateAlpha(int _x, int _y, int _z) public returns (int _alpha) {
    _alpha = _x + this.calculateGamma({_z: _z, _y: _y}); // 매개변수 순서를 지정해서 전달할 때는 매개변수 이름을 지정해야 한다.
  }

  function calculateGamma(int _y, int _z) public returns (int _gamma) {
    _gamma = _y * 3 + 7 * _z;
  }
}

1-6. View and Pure functions

  • view : 상태를 변경하지 않는 함수
  • pure : 상태를 변경하지 않고 읽지도 않는 함수

컴파일러는 상태 수정 또는 읽기 작업의 여부를 확인하지 않기 때문에 viewpure는 문서화 목적으로 사용한다.

상태 변경 동작

  • 상태 변수 쓰기
  • 이벤트 발생
  • 컨트랙트 생성 또는 파기
  • 이더 전송(send() 또는 transfer())
  • Low-level 호출 또는 특정 인라인 어셈블리 옵코드 사용

상태 읽기 동작

  • 상태 변수 읽기
  • 계정 잔액 접근 (this.balance 또는 address.balance)
  • block, tx 에 접근
  • msg 멤버에 접근 (msg.sig, msg.data 제외)
  • pure로 선언하지 않은 함수 호출
  • 특정 인라인 어셈블리 옵코드사용

1-7. Payable functions

payable로 선언된 함수는 이더를 받을 수 있다.

contract StockPriceOracle {
  uint quoteFee = 500;  // getStockPrice() 함수 호출 시 필요한 이더의 양(Wei 단위)
  mapping (string => uint) private stockPrice;

  //...

  function getStockPrice(string _stockTicker) payable returns (uint _stockPrice) {
    if (msg.value == quoteFee) {              // 전송된 이더 수량이 quoteFee와 같은지 비교
      _stockPrice = stockPrice[_stockTicker]; 
    } else {
      revert();                               // 만약 다르다면 트랜잭션을 이전 상태로 복원한다.
    }
  }
}

외부 함수를 호출하는 동안 이더를 전송할 수도 있다.

  function getStockPrice2(string _stockTicker) payable returns (uint _stockPrice) {
    address stockPriceOracleAddress = 0x10abb5EfEcdC09581f8b7cb95791FE2936790b4E;
    uint256 quoteFee = 500;
    string memory stockTicker = "MSFT";

    if (!stockPriceOracleAddress.call.value(quoteFee)   // call()을 사용해서 외부 함수를 호출하는 동안 이더를 전송 
      (bytes4(keccak256("getStockPrice()")),
      _stockTicker)) 
      revert();   // call() 실패 시 이전 상태로 복원
  }
}

1-8. Fallback function

fallback 함수 : 입출력 매개변수를 갖지 않고 함수명이 없는 payable 함수

  • 컨트랙트는 fallback 함수를 오직 한 번 선언할 수 있다.
  • 호출되는 경우
    1. 클라이언트가 함수를 호출했을 때, 해당 함수를 찾지 못했을 경우
    1. 컨트랙트가 payable 함수를 거치지 않고 send(), transfer(), call()을 통해 순전히 이더를 전송 받는 경우
  • send() 또는 transfer()fallback 함수를 호출하면 전송되는 가스를 최소화 할 수 있다.
  • 호출 시 가스가 부족해 실패할 수도 있기 때문에 비용이 많이 드는 작업은 피해야 한다.

아래 코드는 가장 간단하고 전형적인 fallback 함수를 구현한 것이다.
send() 또는 transfer() 호출이 이더 전송을 성공적으로 완료하도록 한다.

function () payable {}

반대로 이더를 받을 필요가 없는 컨트랙트라면 이더를 받지 못하도록 fallback 기능을 구현할 수도 있다.

function () payable {
  revert();
}

주의!
fallback 함수를 올바르게 구현하지 않으면, 악의적인 사용자가 이를 이용해서 다양한 방법으로 컨트랙트를 공격할 수 있다.


1-9. Getter function

컨트랙트에서 public state 변수를 선언하면 자동으로 getter 함수를 생성한다.

  • getter 함수는 상태 변수의 이름을 그대로 가져온다.
  • getter 함수는 암시적으로 publicview로 선언된다.
  • 컨트랙트 내부에서 this를 사용해서 호출할 수 있다.
pragma solidity ^0.4.26;

contract SimpleCoin {
  mapping (address => uint256) public coinBalance;
  address myAccountAddress = msg.sender;

  uint256 myBalance = this.coinBalance(myAccountAddress); // coinBalance는 mapping 타입의 상태 변수인데 함수처럼 사용됨 => getter함수!
}



2. Function modifiers

함수 제어자는 함수를 실행하기 전 또는 후에 수행하여 함수의 동작을 제어한다.

contract FunctionModifier {
  address owner = msg.sender;
  address[] users;
  mapping (address => bool) frozenUser; 
  
  modifier onlyOwner {           
    require(msg.sender == owner);
    _;
  }

  modifier isActive (address _account) {
    require(!frozenUser[_account]);
    _;
  }

  function addUser (address _userAddress) public onlyOwner returns(address[])  {  // 제어자 적용 위치
    users.push(_userAddress);
    return users;
  }
  
  function refund (address _addr) onlyOwner isActive(_addr) {  // 한번에 여러 제어자를 가질 수 있다.
    // ...
  }
}

주의
제어자 함수를 적용할 때 선언한 순서의 반대로 호출된다.
위의 코드에서 refund() 함수에서는 isActive, onlyOwner 순으로 호출된다.



3. Variable declaration, initialization and assignment

이번엔 로컬 함수의 변수를 선언, 초기화 그리고 할당하는 방법에 대해서 살펴보자.

3-1. Implicit initialization

명시적으로 변수를 초기화 하지않아도 기본적으로 비트 0으로 초기화된다.

TypeDefault ValueExample
int, uint (all sizes)0int32 a; // 0
boolfalsebool flag; // false
bytes1~32모든 바이트가 0으로 초기화bytes4 byteArray; // 0x00000000
bytes빈 바이트 배열bytes b; // []
Static Array모든 요소가 빈 값으로 초기화bool[3] flags; // [false, false, false]
Dynamic Array빈 배열int [] values; // []
string빈 문자열string test; // ""
struct각 항목 초기값으로 초기화

NOTE
솔리디티에는 null 값이 없다.

3-2. Delete

delete를 호출해서 기본값으로 다시 초기화할 수 있다.

contract DeleteExample {
  function deleteExample () public pure {
    int32[5] memory fixedSlots = [int32(5), 9, 1, 4, 3];
    delete fixedSlots;	// [int32(0), 0, 0, 0, 0];
  }
}

3-3. Implicitly typed declaration

명시적 초기화로 자료형 유추가 가능하면 변수 타입을 var로 선언할 수 있다.

contract VarExample {
  function varExample () public pure {
    var array1 = [int(5), 9];
  }
}

함수에서 반환된 튜플을 비구조화해서 여러 변수로 재할당할 때 여러 변수를 암시적으로 선언할 수 있다.

var (_alpha, _beta, _success) = calculateInstance.calculate(5);

NOTE
비구조화 튜플: 튜플을 개별 구성요소로 분해하여 별도의 변수에 따라 각각 값을 할당하는 것

3-4. Tuple assignment

암시적 또는 병시적으로 선언된 여러 변수에 튜플을 할당할 때 튜플의 항목 수와 왼쪽에서 할당받는 변수의 수가 같아야 한다.

var (_one, _two, _three, _four) = getFourValues();	// 항목이 4개인 튜플에 값 할당
var (_one, _two, _three, ) = getFourValues();		// 네 번째 항목 빈칸으로 두고 나머지 항목들만 할당
var (_one, _two, _three) = getFourValues();			// 할당 받는 변수의 수(3)가 튜플의 항목 수(4)보다 적기 때문에 에러 발생
profile
블록체인 개발자를 꿈꾸다

0개의 댓글