2022. 3. 30. 13:55ㆍ개발 잡부/블록체인
1. 레슨 2 개요
2줄 요약
1. 멀티플레이어 게임으로 만든다.
2. 좀비가 특별한 먹이를 먹으면 감염시키게 한다.
2. 매핑과 주소
데이터베이스에 저장된 좀비들에게 주인을 설정해보자.
주소
이더리움에 접근하는 계정들의 주소를 담을 수 있다.
한마디로 유저를 특정하는 것이다.
주소는 특정 유저가 소유한다
// 일반 자료형과 같다.
address foo;
매핑
매핑또한 자료형의 일종이며, key-value형식의 자료구조다.
일반 key-value처럼 사용할 수 있다.
uint가 value라면 0으로 초기화된다. (undefined가 아님)
// 매핑 형식
mapping (key_자료형 => value_자료형) 접근제어자 변수명;
// 매핑 선언 예시
mapping (address => uint) public accountBalance;
// 타입스크립트로 표현하면
let accountBalance: { [id: address]: uint };
// 사용법
accountBalance[someAddress] = 10;
정답
...
// 여기서 매핑 선언
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
...
3. Msg.sender
솔리디티에는 모든 함수에서 사용가능한 전역 변수가 있다.
그중에 현재 함수를 호출한 사람의 주소를 가져오는 변수가 있다.
msg.sender
호출하는 내용이 msg에 담겨있을 것으로 보인다.
저 msg도 나중에 분석해보자.
아무튼 이 변수를 통해
호출한 사람을 위조할 수 없게 만든다.
정답
...
function _createZombie(string _name, uint _dna) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
// 여기서 시작
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}
...
4. Require
좀비의 무제한 생성을 막기 위해,
유저당 한번씩만 호출하도록 할 계획이다.
그 과정에서 이 require를 사용하는데,
if와 throw를 합쳤다고 보면 된다.
// require
require(a == b);
// 자바스크립트로 표현
if (a == b) {
throw error;
}
참고로 솔리디티에는 문자열 비교 함수가 없기 때문에,
keccak256 해시 함수를 통해 둘을 비교한다.
require(keccak256(a) == keccak256(b))
정답
...
function createRandomZombie(string _name) public {
// 여기서 시작
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
...
5. 상속
C++의 '그' 상속이 맞다.
문법만 체크하고 가자.
// 부모 컨트랙트
contract A { }
// 자식 컨트랙트 (부모의 public 변수, 함수 사용 가능)
contract B is A {}
정답
...
// 여기서 시작
contract ZombieFeeding is ZombieFactory {
}
...
6. Import
Javascript의 '그' import 맞다.
다만 모듈단위로 export하지는 않는다.
정답
pragma solidity ^0.4.19;
// 여기에 import 구문을 넣기
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {
}
7. Storage vs Memory
변수를 저장하는 공간은
storage와 memory가 있다.
Storage: 블록체인 상에 영구적으로 저장됨.
함수 외부에 선언된 변수 (상태 변수)는 자동으로 storage로 선언됨
Memory: 임시적으로 저장되고, 외부 호출이 끝나면 사라진다.
함수 내부에 선언된 변수는 자동으로 memory로 선언됨.
함수 내부에서 구조체와 배열을 사용하게 된다면,
이 내용은 조금 다르게 적용된다.
Storage: Call by reference처럼 값을 바꾸면 스토리지의 값도 변경된다.
Memory: Call by Value처럼 객체가 복사된다.
// 스토리지
uint storage a;
// 메모리
uint memory b;
정답
...
// 여기서 시작
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
}
...
8. 좀비 DNA
DNA를 도출하는 알고리즘은
일단 간단하게 평균내기로 한다.
정답
...
// 여기서 시작
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
_createZombie("NoName", newDna);
...
9. 함수 접근 제어자 더 알아보기
public / private 이외의 접근제어자인
internal과 external을 살펴보자.
internal: 상속한 컨트랙트에서도 호출 가능함.
다른 컨트랙트에서는 private과 동일한 처리
external: 컨트랙트 바깥에서만 호출 가능함.
컨트랙트 내에서 호출할 수 없다는 것을 제외하면 public과 동일한 처리
정답
...
// edit function definition below
function _createZombie(string _name, uint _dna) internal {
...
10. 좀비가 무엇을 먹나요? (+ 인터페이스, 다중 반환)
크립토키티가 뭔뎈ㅋㅋㅋㅋㅋㅋㅋㅋ
갑자기 크립토키티를 먹는다고 한다.
인터페이스
블록체인 상에 있으면서 우리 소유가 아닌 컨트랙트와 상호작용하려면
인터페이스가 필요하다.
인터페이스의 선언은
C언어의 함수 선언과 비슷하다.
선언만 하고, 정의는 하지 않는다.
다른 컨트랙트와 상호작용할 때,
함수의 특성, 호출 방법, 응답 내용을 알 수 있게 된다.
(뼈대를 제공)
선언시에는 컨트랙트와 마찬가지로
contract로 시작하며, 컴파일러도 컨트랙트와 동일하게 인식한다.
contract TestInterface {
function getNum(address _myAddress) public view returns (uint);
}
함수의 다중 반환
우리는 함수 returns뒤에 (string)이런 식으로 반환값을 설정했다.
솔리디티에서는 다양한 값들을 반환할 수 있다.
(아마 TS에서 오브젝트를 반환하는 것과 같은 메커니즘을 구현하려 한 것 같다.)
function foo() returns (
uint a,
string b,
int c
) {}
정답
...
// 여기에 KittyInterface를 생성한다
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
...
11. 인터페이스 활용하기
그림과 같이 다른 컨트랙트에 접근하기 위한
방식이라고 보면 된다.
즉, 다른 컨트랙트의 모든 속성값을 다 가져올 필요는 없고,
내가 쓸 일부만 가져오면 된다.
인터페이스는 일종의 프로토콜을 선언하는 것과 같다.
예제
contract TestContract {
address targetAddress = ...;
// 인터페이스의 생성자는 목표 컨트랙트의 주소를 받는다.
TestInterface testInterface = TestInterface(targetAddress);
function foo() public {
uint num = testInterface.getNum(msg.sender);
}
}
정답
...
address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
// `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다
KittyInterface kittyContract = KittyInterface(ckAddress);
...
12. 다수의 반환값 처리하기
위에서 설명했던 다양한 반환값을 처리하는 방법이다.
JS나 TS를 써봤다면, Tuple을 생각하면 쉽다.
순서가 있고, 각 위치마다 특정 값에 매핑이 된다.
또한 해체(destructing) 문법이 되는 것 또한 특징이다.
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}
function processMultipleReturns() external {
uint a;
uint b;
uint c;
// 다음과 같이 다수 값을 할당한다:
(a, b, c) = multipleReturns();
// 특정 값만 보고 싶은 경우
(a, , c) = multipleReturns();
}
정답
...
// 여기에 함수를 정의
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna);
}
}
...
13. 보너스: 키티 유전자
우리가 사용중인 좀비 DNA는 16자리인데,
현재 12자리만 쓰고 있다. 나머지 4자리를 '특별한' 특성으로 만들겠다고 한다.
여기서는 마지막 두자리가 99면 고양이 좀비라고 생각해보자.
if
if문법은 자바스크립트와 동일하다.
정답
...
// 여기에 있는 함수 정의를 변경:
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
// 여기에 if 문 추가
if (keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
// 여기에 있는 함수 호출을 변경:
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
...
후기
해체 문법이 있다는게 좀 신기했다.
그래. 새로 나오는 언어인데 좀 편한게 있어야지.
그 외에도 약간씩 감이 잡히기 시작했다.
마치 시장에 칸칸이 있는 가게들처럼
각각의 칸(블록)에 가게들이 있고,
각 가게들은 손님을 처리하기도 하고
다른 가게에서 물건을 떼오기도 하는 느낌이다.
'개발 잡부 > 블록체인' 카테고리의 다른 글
크립토 좀비 1-4. 좀비 전투 시스템 (0) | 2022.03.31 |
---|---|
크립토 좀비 1-3. 고급 솔리디티 개념 (0) | 2022.03.30 |
블록체인 Footprint (0) | 2022.03.30 |
크립토 좀비 1-1. 좀비 공장 만들기 (0) | 2022.03.30 |
클레이튼 Klaytn Count BApp 따라하기 (0) | 2022.03.12 |