솔리디티는 입력 매개변수를 선언할 때 타입을 지정하여 선언한다.
매개변수의 이름 앞에는 _
를 적어주는게 관습이지만 필수는 아니다.
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; }
여러 출력 매개변수를 튜플(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);
}
접근 수준 | 설명 |
---|---|
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 : 외부에서만 접근 가능
}
동일 컨트랙트 내부에서 다른 함수를 직접 접근해서 작동할 수 있다.
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)
이라고 한다.
호출하면 함수 본문은 메모리를 참조하여 매개변수에 직접 접근한다.
외부 컨트랙트를 참조해서 외부 컨트랙트 함수를 작동할 수 있다.
참조할 때는 해당 컨트랙트의 주소가 필요하다.
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;
}
}
함수를 호출할 때 이름을 지정해주면 매개변수를 임의 순서로 전달할 수 있다.
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;
}
}
view
: 상태를 변경하지 않는 함수pure
: 상태를 변경하지 않고 읽지도 않는 함수컴파일러는 상태 수정 또는 읽기 작업의 여부를 확인하지 않기 때문에 view
와 pure
는 문서화 목적으로 사용한다.
상태 변경 동작
- 상태 변수 쓰기
- 이벤트 발생
- 컨트랙트 생성 또는 파기
- 이더 전송(
send()
또는transfer()
)- Low-level 호출 또는 특정 인라인 어셈블리 옵코드 사용
상태 읽기 동작
- 상태 변수 읽기
- 계정 잔액 접근 (
this.balance
또는address.balance
)block
,tx
에 접근msg
멤버에 접근 (msg.sig
,msg.data
제외)pure
로 선언하지 않은 함수 호출- 특정 인라인 어셈블리 옵코드사용
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() 실패 시 이전 상태로 복원
}
}
fallback
함수 : 입출력 매개변수를 갖지 않고 함수명이 없는 payable
함수
- 컨트랙트는
fallback 함수
를 오직 한 번 선언할 수 있다.- 호출되는 경우
1. 클라이언트가 함수를 호출했을 때, 해당 함수를 찾지 못했을 경우
- 컨트랙트가
payable
함수를 거치지 않고send()
,transfer()
,call()
을 통해 순전히 이더를 전송 받는 경우send()
또는transfer()
로fallback
함수를 호출하면 전송되는 가스를 최소화 할 수 있다.- 호출 시 가스가 부족해 실패할 수도 있기 때문에 비용이 많이 드는 작업은 피해야 한다.
아래 코드는 가장 간단하고 전형적인 fallback
함수를 구현한 것이다.
send()
또는 transfer()
호출이 이더 전송을 성공적으로 완료하도록 한다.
function () payable {}
반대로 이더를 받을 필요가 없는 컨트랙트라면 이더를 받지 못하도록 fallback
기능을 구현할 수도 있다.
function () payable {
revert();
}
주의!
fallback
함수를 올바르게 구현하지 않으면, 악의적인 사용자가 이를 이용해서 다양한 방법으로 컨트랙트를 공격할 수 있다.
컨트랙트에서 public state 변수
를 선언하면 자동으로 getter
함수를 생성한다.
getter
함수는 상태 변수의 이름을 그대로 가져온다.getter
함수는 암시적으로public
과view
로 선언된다.- 컨트랙트 내부에서
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함수!
}
함수 제어자는 함수를 실행하기 전 또는 후에 수행하여 함수의 동작을 제어한다.
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
순으로 호출된다.
이번엔 로컬 함수의 변수를 선언, 초기화 그리고 할당하는 방법에 대해서 살펴보자.
명시적으로 변수를 초기화 하지않아도 기본적으로 비트 0으로 초기화된다.
Type | Default Value | Example |
---|---|---|
int, uint (all sizes) | 0 | int32 a; // 0 |
bool | false | bool 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 값이 없다.
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];
}
}
명시적 초기화로 자료형 유추가 가능하면 변수 타입을 var
로 선언할 수 있다.
contract VarExample {
function varExample () public pure {
var array1 = [int(5), 9];
}
}
함수에서 반환된 튜플을 비구조화해서 여러 변수로 재할당할 때 여러 변수를 암시적으로 선언할 수 있다.
var (_alpha, _beta, _success) = calculateInstance.calculate(5);
NOTE
비구조화 튜플: 튜플을 개별 구성요소로 분해하여 별도의 변수에 따라 각각 값을 할당하는 것
암시적 또는 병시적으로 선언된 여러 변수에 튜플을 할당할 때 튜플의 항목 수와 왼쪽에서 할당받는 변수의 수가 같아야 한다.
var (_one, _two, _three, _four) = getFourValues(); // 항목이 4개인 튜플에 값 할당
var (_one, _two, _three, ) = getFourValues(); // 네 번째 항목 빈칸으로 두고 나머지 항목들만 할당
var (_one, _two, _three) = getFourValues(); // 할당 받는 변수의 수(3)가 튜플의 항목 수(4)보다 적기 때문에 에러 발생