본 글은 freeCodeCamp.Org의 Youtube 영상 'Solidity, Blockchain, and Smart Contract Course – Beginner to Expert Python Tutorial'와 관련 코드인 SmartContract의 Github 코드를 기초로 작성되었다.
Youtube 영상 링크: https://www.youtube.com/watch?v=M576WGiDBdQ&t=10336s
Github 코드 링크: https://github.com/smartcontractkit/full-blockchain-solidity-course-py
오늘의 코드: https://github.com/PatrickAlphaC/simple_storage/blob/main/SimpleStorage.sol
이번 포스팅은 유튜브 영상 01:31:00~02:09:32에 해당하는 내용이다.
지난 포스팅에서 블록체인에 관한 글 읽느라 고생 많았다! 이제 본격적으로 Solidity 코딩에 대해 공부해보자. Solidity 코딩은 아래 사이트에서 진행하면 된다. 이 사이트가 'Remix'다.
http://remix.ethereum.org/
다른 프로그래밍 언어들과 다르게 넷상에서 컴파일(코드를 컴퓨터가 이해할 수 있는 언어로 변경하는 과정, 이를 수행하는 프로그램을 '컴파일러'라고 함)되므로 별도의 프로그램 설치가 필요하지 않다. 위 사이트는 앞으로 코딩을 진행하는 내내 이용되니 주소를 외워두면 좋다.
지금 설명하는 내용은 영상을 보면 훨씬 쉽게 따라할 수 있으니 영상을 보기를 추천한다. 오늘 포스팅에 해당하는 영상 시간 처음부터 보면 된다. Remix에 들어가면 'Featured Plugins' 아래 'SOLIDITY'가 있다. 클릭해주고 좌측 상단에 종이 두 개 겹쳐진 모양의 문양 클릭하면 'contracts' 폴더가 뜬다. 폴더에 대고 우클릭-새로 생긴 파일에 'SimpleStorage.sol'입력하면 된다. 그러면 다음과 같은 창이 뜬다.
이 창에 코드를 입력하면 된다. 나머지 환경은 설정이 되어있으니 바로 시작하면 된다. 시작하기 전에 Solidity가 무슨 프로그래밍 언어인지만 간략하게 설명하겠다. 스마트 컨트랙트는 코드를 활용한 전자 계약이다. 따라서 스마트 컨트랙트를 실행하기 위한 코딩은 현실 세계의 계약서 작성과 비슷하다고 볼 수 있다. 계약서 작성을 위한 프로그래밍 언어가 Solidity이다. 즉, Solidity는 거래 당사자들 간 계약을 체결하기 위해 만들어진 계약 특화 언어이다.
자! 그럼 본격적으로 코딩을 시작해보자. 처음 입력할 코드를 보자.
pragma solidity >=0.6.0 <0.9.0;
solidity는 컴파일러 버전이 지속적으로 업데이트된다. 버전마다 코드 활용 방식이 조금씩 달라지기 때문에 사용할 버전을 지정해주어야 한다. 'pragma solidity'는 버전을 지정하겠다는 선언이고, 그 뒤에 버전을 명시해주면 된다. 마지막 ';'는 선언의 마무리를 표현하는 기호이다. 위 예시에서는 버전 0.6.0보다는 크거나 같고(>=0.6.0) 0.9.0보다는 작은 버전(<0.9.0)을 사용하겠다고 이야기한다. 여기서 의문이 하나 발생한다. '버전마다 코드 활용 방식이 다른데 범위로 버전을 지정하면 버전끼리 충돌하는 코드는 어떻게 처리하지?'라는 의문이 든다. 그러나 컴파일은 저 범위 중 하나의 버전으로 컴파일이 된다. 예컨대 버전 0.7.0과 버전 0.8.0이 동시에 지정되는 게 아니라 0.7.0 하나로 버전 선택이 된다. 구체적으로 어떤 버전이 활용되는지 확인하고 싶다면 좌측 상단의 solidity 문양을 클릭하면 된다.
좌측 상단 'COMPILER' 아래 박스에서 활용되는 컴파일러 버전을 확인할 수 있고(위 사진에서는 0.8.7), 이를 수동으로 조절할 수도 있다. 그러나 이때 박스의 컴파일러 버전이 'pragma solidity'에 의해 지정되는 버전과 다르다면 'pragma solidity'에 맞추어 박스 안에 나타는 버전이 자동으로 변경된다. 예컨대 박스 안의 버전을 0.5.9로 설정해두고 우리가 처음 본 코드(pragma solidity >=0.6.0 <0.9.0;)를 작성한 상태에서 컴파일 버튼인 파란색 버튼(Compile SimpleStorage.sol)을 누르면(단축기 CTRL+S) 아래 화면과 같이 박스 안의 컴파일러 버전이 변경된다. 아래 화면에서는 컴파일러 버전이 0.8.15로 변경되었음을 확인할 수 있다.
버전 지정 방법에는 총 3가지가 있다. 첫 예시처럼 등호와 부등호를 사용하여 범위를 지정하는 방법이 하나, 버전 하나를 명시하는 방법이 둘, '^'기호를 통해 범위 지정을 제한적으로 하는 방법이 셋이다. 예시를 들겠다.
첫째: pragma solidity >0.6.0 <0.9.0;
둘째: pragma solidity 0.6.0;
셋째: pragma solidity ^0.6.0;
첫째 방법은 앞서 설명했고, 둘째 방법은 직관적으로 0.6.0으로 버전을 정하고 있음을 알 수 있다. 셋째 방법은 설명 없이는 어떤 선언인지 이해하기 힘들다. 위 예시에서는 0.6.X 버전을 활용하겠다는 선언이다. 즉, 0.6.0보다는 크고 0.7.0보다는 작은 버전을 활용하겠다는 선언이다. 추가 설명 없이 예시만으로 '^'가 어떤 의미를 갖는지 충분히 파악되었을 테니 일반화는 굳이 하지 않겠다.
다음 코드를 보자
Contract SimpleStorage{
...
}
'Contract'는 Solidity에서 가장 핵심적인 선언이라고 볼 수 있다. 이 선언은 곧 계약서를 작성하겠다는 의미이다. 'Contract' 뒤에 따라오는 'SimpleStorage'는 이 계약서의 이름, '{}'는 계약의 내용을 담는 공간이다. 'Contract'를 작성하고 컴파일을 하면 '{}' 안의 내용이 이행된다. 이제 계약 내용을 채워보자.
uint256 favoriteNumber;
struct People {
uint256 favoriteNumber;
string name;
}
'uint256 favoriteNumber'은 변수 선언이다. 이는 'favoriteNumber'라는 이름을 가진 256비트짜리 양의 정수(unsigned integer) 하나를 메모리에 할당한다. 즉 'favoriteNumber' 안에 256비트 양의 정수 하나를 담을 수 있게 된다. 'uint256 favoriteNumber = 5;' 이런 방식으로 선언을 하면 favoriteNumber 안에 양의 정수 5가 담긴다.
'struct People'은 구조체 선언으로, 구조체 안에 여러 변수나 함수를 담을 수 있다. 이 선언으로 'People'이라는 구조체 안에 'favoriteNumber'라는 256비트 양의 정수 하나와 'name'이라는 문자열 하나가 담기게 되었다.
이때 첫 줄에 선언된 favoriteNumber는 People 구조체 안에 선언된 favoriteNumber와는 다른 변수임을 주의해야 한다.
People[] public people;
mapping(string => uint256) public nameToFavoriteNumber;
위 코드의 첫 줄은 'People' 구조체 여럿을 할당한다. '[]'은 배열 표시인데, 대괄호 안의 숫자가 배열 번호이고, people은 배열 이름이다. 배열 번호는 0부터 시작하여 'people[0]', 'people[1]', 'people[2]', ... 이런 식으로 확장된다. 'people[0]'에는 첫 번째 구조체 정보가(favorite number와 name), 'people[1]'에는 두 번째 구조체 정보가, ..., 'people[n-1]'에는 n 번째 구조체 정보가 담긴다. 즉 이 배열을 선언함으로써 여러 사람의 'favoriteNumber'와 'name'을 배열 안에 담을 수 있게 되었다.
'public'은 solidity의 '가시성(visibility) 키워드' 중 하나이다. 가시성 키워드는 함수나 값에 접근할 수 있는 범위를 지정해준다. 가시성 키워드에는 총 4가지(public, external, internal, private)가 있다. 각각을 정리하면 다음과 같다.
public: 해당 contract 내부, 외부에서 모두 호출 가능
external: 해당 contract 외부에서만 호출 가능
internal: 해당 contract 내부와 상속된 contract에서만 호출 가능
private: 해당 contract 내부에서만 호출 가능
'mapping'은 '키(key)'로 '값(value)'을 참조하는 데이터 형식이다. 위 코드에서는 'nameToFavoriteNumber'이라는 mapping을 통해 문자열(string)로 256비트 양의 정수(uint256)를 참조한다. mapping의 활용 형식은 이후에 확인하겠다.
function store(uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
프로그래밍에서 '함수'는 특정한 기능을 하기 위한 코드 집합이다. 'function'을 통해 함수가 선언된다. function 뒤에는 해당 함수의 이름이 나온다. 이 함수의 이름은 store이다. 괄호 안의 내용은 입력값이다. 입력값은 '자료형 이름'의 형식으로 제시된다. 위 예시에서는 입력값이 'uint256 _favoriteNumber'의 형식으로 제시되었음을 확인할 수 있다. {} 안에는 함수의 기능을 정의하기 위한 코드가 들어간다. 위 예시에서는 본 함수가 입력된 256비트 양의 정수를 favoriteNumber에 저장하는 기능을 수행하고 있다. '=' 기호는 '=' 오른쪽의 값을 '=' 왼쪽에 저장하는 역할을 한다.
function retrieve() public view returns (uint256){
return favoriteNumber;
}
위 코드에서 함수는 'retrieve'라는 이름으로 정의되어 있다. 입력값이 비어있는 반면 반환값이 정의되어 있다. 'returns'는 해당 함수가뒤따라나오는 () 안 형태의 값을 반환할 것임을 알려준다. 위 코드에서는 uint256 형태의 값이 반환될 것임을 알 수 있다. 반환하는 값은 {} 안에서 찾을 수 있다. 'return'은 뒤잇는 값을 반환하겠다는 선언이다. 따라서 이 함수에서는 favoriteNumber가 반환된다.
'view'라는 키워드가 새로 등장했다. 'view'라는 키워드는 가시성 키워드 앞뒤 어디든 올 수 있다. 'view' 키워드가 붙으면 그 함수 내에서는 함수 밖 변수의 값을 변경할 수 없다. 'view'의 뜻인 '보다'에서 느껴지듯이 외부 변수에 개입하지 말고 보기만 하라는 듯 하다.
'pure' 또한 'view'와 비슷한 성격의 키워드다. pure 가시성 키워드 앞뒤 어디에든 붙어 해당 함수가 외부 변수에 접근하지 못하도록 한다. 'pure'는 그 뜻인 '순수'처럼 다른 변수와 해당 함수가 얽히지 않게 한다.
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People(_favoriteNumber, _name));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
위 함수 addPerson은 입력값이 2개이고, 각 입력값이 쉼표로 구분되고 있다. 2개 이상의 입력값이 존재할 때는 (입력값 이름, 입력값 이름, ...)이런 방식으로 입력값을 제시한다. {} 안의 함수 내용을 보자. 첫째줄은 배열.push(배열에 넣을 값)을 표현하는 함수이다. push 함수는 ()안의 '배열에 넣을 값'을 n 번째 배열, 즉 예시에서는 people[n-1]에 넣고 다음 번에 함수가 다시 실행되었을 때 같은 동작을 n+1 번째 배열에 대해 수행하도록 한다. 함수가 처음 호출될 때는 이를 첫 번째 배열에 대해 실행한다. 위 예시에서는 _favoriteNumber와 _name이 담긴 People 구조체를 입력 받아 people[n-1]에 넣고 다음 함수 호출시 같은 과정이 people[n]에 대해 이루어지도록 한다.
다음 줄에서는 mapping의 구체적 활용 양상이 제시된다. mapping은 'mapping이름[키]'가 값을 참조하는 형태로 활용된다. 즉 위 예시에서는 nameToFavoriteNumber[_name]이 참조하는 값에 _favoriteNumber를 입력하고 있다.
// SPDX-License-Identifier: MIT
코드를 작성하기 전 첫 줄에 해당 표시를 해두는 게 좋다. 안 하면 solidity 컴파일러가 한 소리 한다. 이 표시는 이후 제시될 코드의 라이센스를 표현한다. 본 포스팅이 기초하는 코드는 MIT 라이센스를 따른다. 이는 해당 코드가 오픈 코드로, 누구나 자유롭게 활용할 수 있음을 의미한다.
이제 코드 동작 양상을 구체적으로 보이겠다. 먼저 'SimpleStorage.sol'에 이번 시간에 작성한 코드를 처음부터 끝까지 입력해보자. (Github 링크에서 복사해서 붙여넣는 게 편하다.)
파란색 컴파일 버튼을 눌러 컴파일을 한 후 왼쪽에 있는 이더리움 모양의 문양을 누르자. 그러면 아래와 같이 주황색 Deploy 버튼이 보인다. 이를 눌러주자.
그러면 다음처럼 'Deployed Contracts'에서 본 코드의 계약 인터페이스가 생성되었음을 확인할 수 있다.
remix가 블록체인 네트워크와 연결된 상태를 가정하자. 주황색 버튼은 누르면 네트워크 상에 반영이 된다. 따라서 수수료가 발생한다. 반면 파란색 버튼은 눌러도 계약 내용을 확인만 하는 등 네트워크에 반영되지 않는다. 따라서 수수료도 발생하지 않는다.
현재는 remix가 블록체인 네트워크와 연결되어 있지 않으니 자유롭게 실제 값을 입력하고 버튼을 눌러보면서 Deploy된 계약의 실행 및 확인 방법을 알아가자. 팁을 주겠다.
1. store, retrieve 버튼은 favoriteNumber와 연관되어 있고, addPerson, nameToFavoriteNumber, people 버튼은 people 구조체와 연관되어 있다. favoriteNumber와 people 구조체는 별개다. 따라서 (store,retrieve)와 (addPerson,nameToFavoriteNumber,people)은 연관되어 있지 않다. store에 값을 입력해놓고 people에서 이를 찾는 바보같은 짓은 하지 말자.(필자가 처음에 그랬다...)
2. addPerson의 입력 예시: "Ashley", 1026
addPerson에 여러 사람과 여러 숫자를 입력해보자! 최소 세 사람과 그에 대응하는 숫자 3개는 입력하자.
3. store의 입력 예시: 3
store에는 그냥 아무 정수나 입력하면 된다. 새로운 정수를 입력할 때마다 기존에 저장되어 있던 정수는 지워지고 새로운 정수로 대체된다.
4. nameToFavoriteNumber 입력 예시: "Ashley"
addPerson에 입력했던 값 중 문자열에 해당하는 값을 입력하면 대응하는 정수가 출력된다.
5. people 입력 예시: 0
people에 0을 입력하면 addPerson에 처음으로 입력했던 값이 출력된다. people에 1을 입력하면 addPerson에 두 번째으로 입력했던 값이 출력된다. people에 n-1을 입력하면 addPerson에 n 번째로 입력했던 값이 출력된다.
6. retrive 누르면 store 버튼을 통해 입력한 정수 하나가 출력된다.
Youtube의 흐름을 따라가면서 초보자도 쉽게 이해할 수 있게 세심하게 설명하려고 했다. 그러나 필자가 시간적 여유가 많이 없어서 이 목표를 잘 수행하지 못했다. 필자도 공부하는 입장이라 모든 개념을 엄밀하게 설명하기가 힘들기도 하다. 일단 7주 동안 포스팅을 목표한대로 완료한 후 시간 여유를 가지고 글을 보완해나갈 계획이다. 앞으로의 7주도 함께해주길 부탁한다!!! 감바스에 와인 한 잔이 그리운 밤이다. 근데 이제 성시경 노래를 곁들인...
Github Code License:
MIT License
Copyright (c) 2021 SmartContract
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.