블록체인 정보 맛보기 블록체인 [이더리움을 통해 맛보기] (3)
포스트
취소

블록체인 [이더리움을 통해 맛보기] (3)

솔리디티 (Solidity)

지난 포스트에선 Remix IDE에서 솔리디티 예제 파일을 컴파일 하고 배포하는 것을 해 보았습니다.

오늘 할 것은 앞서 한 것들을 바탕으로 실제로 솔리디티를 배우고 소스를 코딩하는 것을 목표로 할 것입니다.

솔리디티의 구조

Untitled

지난 사용했었던 예제 .sol 파일을 라인별로 분석해 솔리디티의 기본 구조를 알아봅시다.

1 라인: 소스코드의 라이선스를 GPL-3.0으로 명시

3 라인: Version Pragma: 소스코드가 이용하는 컴파일러 버전 명시

  • Sematic versioning을 따르고 있습니다. major.minor.patch
  • ^(캐럿 연산자): ‘이상’ ← 이하 이상 할 때 그 이상입니다.

9 라인: 컨트랙트의 범위를 나타내는 부분으로 중괄호로(28라인과 함께) 감싸져 있는 것을 보실 수 있습니다.

11라인: 상태 변수(State Variable)이 선언되어 있습니다.

State Variable

  • 블록체인(contract storage)에 값이 저장되는 변수
  • 상태 변수의 접근 제어자(Visibility)를 external, public, priavte와 같이 지정 가능
  • 기본형, 고조체, 배열 등 다양한 자료형이 존재

17라인: 함수(Function)이 선언되어 있습니다.

Function

  • 컨트랙트 단위의 기능(해당 스마트컨트랙이 할 수 있는 기능)
  • 매개 변수, 제어자, 반환값 지정 가능
  • 함수 내부서 상태 변수의 값을 변경하거나 읽을 수 있음 (Read&Write)

솔리디티 문법

기본형 Primitives 타입

  • 논리형 bool: true or false
  • 정수형 uint: unsigned integer int: signed integer
  • 8 ~ 256 bit를 표현할 수 있으며, uint는 uint256과 같습니다
  • 주소형 address: 이더리움의 주소를 표현
  • 바이트형 bytes# or byte[]: 데이터를 바이트로 표현할 수 있습니다

접근 제어자 Visibility

 privateinternalpublicexternal
설명- 컨트랙트 내에서만 접근 가능- 현재 컨트랙트와 자식 컨트랙트에서 접근 가능- 현재 컨트랙트, 자식 컨트랙트, 외부 컨트랙트 및 주소에서 접근 가능- 외부 컨트랙트와 주소에서 접근 가능
(내부 접근 불가)
State VariablesOXOO
FunctionsOOOO
  • public으로 되어 있으면 외부 컨트랙트에서 해당 상태 변수를 바로 조회할 수도 있습니다.

Solidity 예제

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract ParentTest {
    // State variables 테스트
    string private privateVar = "private variable test";
    string internal internalVar = "internal variable test";
    string public publicVar = "public variable test";

    // Private function
    function privateFunc() private pure returns (string memory) {
        return "private test function called";
    }

    // Private fucntion을 테스트할 fucntion
    function testPrivateFunc() public pure returns (string memory) {
        return privateFunc();
    }

    // Internal function
    function internalFunc() internal pure returns (string memory) {
        return "internal test function called in Parent Contract";
    }

    // Internal fucntion을 테스트할 fucntion
    function testInternalFunc() public pure virtual returns (string memory) {
        return internalFunc();
    }

    // Public functions 테스트
    function publicFunc() public pure returns (string memory) {
        return "public test function called";
    }

    // External functions
    function externalFunc() external pure returns (string memory) {
        return "external test function called";
    }
}

contract ChildTest is ParentTest {
    // Internal function call be called inside child contracts.
    function testInternalFunc() public pure override returns (string memory) {
        return internalFunc();
    }

	function testInternalFuncInChild() public pure returns (string memory){
        // return privateFunc(); //에러 발생
        return internalFunc();
    }
}
  • 타 언어와 다른 특징으로는 함수의 return타입을 뒤에 선언한다는 점
  • internal 함수는 자식에서 호출이 가능
  • public 함수는 자식 뿐만 아닌 외부에서도 모두 호출이 가능
  • 이 예시를 컴파일 해서 배포(배포 방법은 이전 포스트 참고)한 후 각 함수를 호출해보면 private 와 public과 같은 Visibility를 잘 확인하실 수 있습니다.

자주 쓰는 자료형(타입)

배열 Array

  • 고정 길이, 가변 길이 배열이 존재합니다
  • 배열형 자료구조 제어 방법 index에 접근 push, pop, delete 사용
  • 함수 내에서 로컬 변수로 배열을 사용하기 위해서는 고정 길이로 선언해야 합니다
  • 인덱스는 0부터 시작합니다

    Solidity 예제

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity >=0.7.0 <0.9.0;
    
    contract Array {
        // 고정 길이 배열, 모든 원소는 0으로 초기화 됨
        uint[10] public fixedSizeArr;
    
        // 가변길이 배열
        uint[] public arr; // 초기화 안 한 상태
        uint[] public arr2 = [1, 2, 3]; // 같은 라인에서 초기화도 같이 한 상태
    
        // 배열의 접근(조회) 방법
        function get(uint i) public view returns (uint) {
            return arr2[i];
        }
    
        // 새 원소를 배열에 추가
        // 이 함수가 호출되고 나면 배열의 크기를 확인해 보자
        function push(uint i) public {
            arr.push(i);
        }
    
        // 배열에서 마지막 원소 삭제
        // 이 함수가 호출되고 나면 배열의 크기를 확인해 보자
        function pop() public {
            arr.pop();
        }
    
        // delte 지시자를 사용하지만 실상은 특정 인덱스의 원소를 0으로 초기화시키는 동작을 함
        // 이 함수가 호출되고 나면 배열의 크기를 확인해 보자
        function remove(uint index) public {
            delete arr[index];
        }
    
        // 배열의 크기를 반환하는 함수
        function getLength() public view returns (uint) {
            return arr.length;
        }
    
        // 전체 배열을 반환하는 함수
        function getArr() public view returns (uint[] memory) {
            return arr;
        }
    
        // 고정길이의 5칸짜리 배열을 생성하고 그것을 반환해주는 함수
        function createArray() external pure returns (uint[] memory){
            // create array in memory, only fixed size can be created
            uint[] memory a = new uint[](5);
            return a;
        }
    }
    

매핑 Mapping (자주 쓰임)

예시를 통해 Mapping에 대해 다음을 배워봅시다.

  • 매핑형 선언
  • 접근, 추가, 삭제
  • 매핑에 저장된 key의 목록을 얻을 수 있는 방법을 제공하지 않습니다

Solidity 예제

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;

contract Mapping {
    // 매핑의 선언
    // address(주소)를 uint로 매핑
    mapping(address => uint) public addrToUint;

    // 매핑 내의 키 값으로 값을 연결함
    // 만약 해당하는 키가 없으면, 해당 타입의 기본 값을 반환함, 0.
    function get(address _addr) public view returns (uint) {
        return addrToUint[_addr];
    }

    // 매핑 내 address 키에 해당하는 unit 값을 업데이트
    function set(address _addr, uint _i) public {
        addrToUint[_addr] = _i;
    }

    // 매핑 내 address 키에 해당하는 unit 값을 기본 값으로 초기화, 0.
    function reset(address _addr) public {
        delete addrToUint[_addr];
    }

    // 이런식으로 매핑의 값을 uint 배열 때처럼 불러오는 것은 불가능하다(에러)
    // 왜냐하면 매핑은 내부적으로 키를 저장해 놓는 것이 아니라 키 자체의 sha3해시에 의해 계산된
    // 상태 메모리(state memory)에 저장된 값만 저장한다. 따라서 매핑의 조회는 원래 키가 제공되어야만 이를 계산하여
    // 조회할 수 있게 구현되어 있기에 반드시 key가 있는 상태로 조회되어야 한다.
    // function getMapping() public view returns (mapping memory){
    //     return addrToUint;
    // }
}

사용자 선언 자료형 - Struct

다음을 예시를 활용해 배워봅시다. Struct는 한글로 구조체라고도 합니다.

  • 여러 자료형을 하나의 관점으로 묶어서 관리하고자 할 때 선언합니다. (마치 C언어 같음)
struct Todo{
	string todoText;
	bool isComplete;
}
  • 구조체의 Array, Mapping 의 값으로 지정이 가능합니다.
  • 구조체 생성, 접근, 변경
  • 함수 안에서 struct 상태 변수 참조 방법

Solidity 예제

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;

contract Struct {

    //구조체의 선언
    struct Todo {
        string text;
        bool boolean;
    }

    // 구조체의 배열 선언
    Todo[] public structTodoArray;

    // 주소를 Todo 타입으로 매핑한다.
    mapping(address => Todo) public addrToStruct;

    // 새로운 구조체를 생성해서 구조체 배열에 푸시
    // 접근방식 1
    function create1(string memory _text) public {
        structTodoArray.push(Todo(_text, false));
    }

    // 접근방식 2
    function create2(string memory _text) public {
        structTodoArray.push(Todo({text: _text, boolean: false}));
    }

    // 접근방식 3
    function create3(string memory _text) public {
        Todo memory s;
        s.text = _text;
        structTodoArray.push(s);
    }

    // 구조체 배열 내 특정 인덱스의 Todo 인스턴스의 text를 변경
    function updateText(uint _index, string memory _text) public {
        Todo storage s = structTodoArray[_index];
        s.text = _text;
    }

    // 불리언(Boolean) 값을 토글
    function updateBoolean(uint _index) public {
        Todo storage s = structTodoArray[_index];
        bool current = s.boolean;
        s.boolean = !current;
    }
}

함수 function

예제를 통해 다음을 배워봅시다.

  • 함수 선언 방법
  • 매개변수 유무, 반환 값 유무
  • view, pure 함수의 특징
  • 2개 이상의 값을 반환하도록 선언

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;

contract Function {

    //여러 상태변수들 선언
    uint public num = 1;

    uint public a = 1;
    string public s = "hello solidity";
    bool public b = true;

    // 파라미터와 리턴값이 없는 함수
    function addOne() public {
        num++;
    }

    // 하나의 파라미터와 리턴값이 있는 함수
    function addNumber(uint x) public returns (uint) {
        num += x;

        return num;
    }

    // view - 상태변수들을 변경하지 않는다, 단순히 읽음. 가스가 안듬.
    function addAndReturn(uint x) public view returns (uint) {
       return num + x;
    }

    // pure - 상태 변수를 읽거나 수정하지 않음. 가스가 안듬.
    function add(uint x, uint y) public pure returns (uint) {
       return x + y;
    }

    // 여러 값들을 리턴하는 방법.
    function returnMany() public view returns (uint, string memory, bool) {
        return (a, s, b);
    }

}

Data location

  • 배열이나 구조체처럼 복합타입(Complex type)의 경우 데이터의 위치(Data location)에 대한 어노테이션(Annotation)이 추가됩니다.
  • 메모리(memory)와 저장소(storage), 콜데이터(calldata)로 구분되는 데이터의 위치는 각 타입별로 기본값이 있으나 필요에 따라서 재지정이 가능합니다.

    • 메모리(memory) : 함수 내에서 임시로 데이터(storage 등)를 저장 할 때 사용하는 변수이다. 임시 데이터(temporary data)
    • 저장소(storage) : 블록체인 상에 영구이 저장된다. 영구데이터(permanent data) 영역에 데이터가 저장되므로 다른 키워드에 비해 큰 비용을 초래한다.
    • 콜데이터(calldata) : 리턴 파라미터를 제외한 외부 함수의 파라미터들, 데이터를 호출하고 메모리와 비슷하게 동작 → 함수에 전달되는 매개 변수처럼 변경 불가능하고 임시적인 데이터 저장 영역임.

    Solidity 예제

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity >=0.7.0 <0.9.0;
    
    contract HamburgerFactory {
        struct Hamburger {
    	    string name;
    	    string status;
    	}
    
    	Hamburger[] Hamburgers;
    
        //햄버거를 초기화하고 배열에 햄버거를 넣음
        function createHamburger(string memory _text) public {
            Hamburgers.push(Hamburger({name: _text, status: "not eat"}));
        }
    
    	function eatStorageHamburger(uint _index) public {
            // storage로 선언된 myHamburger로 햄버거의 인덱스를 참조전달함
    	    Hamburger storage myHamburger = Hamburgers[_index];
            myHamburger.status = "eaten"; //따라서 수행되고 나면 값이 바뀜
    	}
    
    	function eatMemoryHamburger(uint _index) public view{
            // 메모리 변수의 선언
    	    Hamburger memory myHamburger = Hamburgers[_index];
    	    myHamburger.status = "eaten"; //상태가 변해도 블록체인에는 영향X.
    	}
    
        function getStatusHamburger(uint _index) public view returns(string memory){
            return Hamburgers[_index].status; //해당 값이 memory 형태에 담겨서 return됨
        }
    }
    

제어문

제어문은 타 언어와 매우 유사하므로 예시를 위주로 설명하겠습니다.

조건문 If-Else

//함수 내에서만 사용 가능함.
if(x < 10){
	return 0;
} else if (x < 20){
	return 1;
} else {
	return 2;
}

// 삼항 연산자
	return _x < 10 ? 1 : 2;

반복문 for / while

//함수 내에서만 사용 가능함.
for (uint i = 0; i < 10; i++) {
	if (i == 3) {
		continue;
	}
	if (i == 5) {
		break;
	}
}

uint i;
//함수 내에서만 사용 가능함.
while (i < 10){
	i++;
}
  • 반복문 사용시 주의사항: 이더리움은 튜링 완전머신으로 반복문을 제대로 지원하지만 함수를 수행할 때 수행 시간에 따라 가스가 발생하기 때문에 로직을 비효율적으로 하면 많은 돈이 소모될 수 있습니다.

화폐 단위

  • 화폐 단위에는 ether와 wei, gwei가 있습니다.
  • 이더리움 내에선 소수점을 허용하지 않기 때문에 이와 같은 방식을 사용합니다.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;

contract EtherUnits {
    uint public oneWei = 1 wei;
    uint public oneGwei = 1 gwei;
    uint public oneEther = 1 ether;

    // 1 wei is equal to 1
    bool public isOneWei = 1 wei == 1;

    // 1 ether is equal to 10^18 wei
    bool public isOneEther1 = oneEther == 1e18;

    // 1 ether is equals to 10^9 gwei.
    bool public isOneEther2 = oneEther == 10**9 * oneGwei;

    // 1 gwei is equals to 10^9 wei.
    bool public isOneGwei = oneGwei == 10**9 * oneWei;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

프로그래머스 Level2 카카오프렌즈 컬러링북 풀이

블록체인 [이더리움을 통해 맛보기] (4)