매핑과 주소
- 새로운 자료형
Address
- 이더리움 블록체인은 계정들로 이루어져 있음
- 계정은 이더의 잔액을 가짐 (ETH : 이더리움 블록체인상의 통화)
- 계정을 통해 다른 계정과 이더를 주고 받을 수 있음
- 각 계정은 특정 계정을 가리키는 고유 식별자인 주소를 가짐
- 주소 → 특정 유저 혹은 스마트 컨트랙트가 소유함
Mapping
- 구조화된 데이터를 저장하는 방법
- Key - Value 저장소
- 데이터를 저장하고 검색하는데 이용
// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장
// key -> address / value -> uint
mapping (address => uint) public accountBalance;
// 혹은 userID로 유저 이름을 저장/검색하는데 mapping 사용
// key -> uint / value -> string
mapping (uint => string) userIdToName;
Msg.sender
- 현재 함수를 호출한 사람 혹은 스마트 컨트랙트의 주소를 가리킴
- 모든 함수에서 이용 가능한 특정 전역 변수들 중 하나
- 컨트랙트는 누군가가 컨트랙트 함수를 호출하기 전까진 블록체인 상에서 아무것도 안함
→ 항상 msg.sender 가 필요
// 구조화된 데이터를 저장하는 방법
mapping (address => uint) favoriteNumber;
function serMyNumber(uint _myNumber) public {
// `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트
// 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일
// address(key) = msg.sender, uint(value) = _myNumber
favoriteNumber[msg.sender] = _myNumber;
}
function whatIsMyNumber() public view returns (uint) {
// sender의 주소에 저장된 값을 불러옴
// sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`
return favoriteNumber[msg.sender];
}
+ JS와 마찬가지로 솔리디티에서도 uint를 ++를 이용해 증가시킬 수 있음
uint number = 0;
number++; // `number`는 이제 `1`이다
Require
- 특정 조건이 참이 아닐때 함수가 에러 메시지를 발생하고 실행을 멈추게 됨
- 함수를 실행하기 전에 참이어야하는 특정 조건을 확인하는데 유용
function sayHiVitalik(string _name) public returns (string) {
// _name이 "Vitalik"인지 확인
require( keccak256(_name) == kecceak256("Vitalik") );
return "Hi!"; // 참이면 실행
- 솔리디티는 고유의 string 비교 기능을 가지고 있지 않음
→ 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단
상속
긴 컨트랙트 하나를 만드는 것보다 코드를 잘 정리해서 여러 컨트랙트에 코드 로직을 나누는것이 합리적일때가 있음
→ 이를 관리하기 쉽게 해주는게 컨트렉트 상속 → is
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
// BabyDoge 컨트렉트는 Doge 컨트렉트를 상속
// catchphrase() 함수와 anotherCatchphrase()에 모두 접근 가능
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Suxh Moon BabyDoge";
}
}
Import
- 다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때 사용
// ./ -> 현재 컨트랙트와 동일한 폴더에 있다는 의미
import "./someothercontract.sol";
Storage VS Memory
- 변수를 저장할 수 있는 공간
- 솔리디티가 알아서 처리해준다고 설명 되어 있는데 바뀐걸로 알고 있음
Storage
- 블록체인 상에 영구적으로 저장되는 변수
- 상태변수 (함수 외부에 선언된 함수)
Memory
- 임시로 저장되는 변수
- 함수 내에 선언된 변수 (호출이 종료되면 사라짐)
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;
function eatSandwich(uint _index) public {
// `storage` 키워드를 활용하여 다음과 같이 선언
Sandwich storage mySandwich = sandwiches[_index];
// ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터
// 그리고 아래의 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경
mySandwich.status = "Eaten!";
// 단순히 복사를 하고자 한다면 `memory`를 이용
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다.
anotherSandwich.status = "Eaten!";
// 그리고 위 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로
// `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다
sandwiches[_index + 1] = anotherSandwich;
// ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
}
}
함수 접근 제어자
Private
- 같은 컨트랙트에서 호출 가능
Internal
- 함수가 정의된 컨트랙트를 상속한 컨트랙트에서도 호출 가능
- 위 특징을 제외하면 Private과 동일
Public
- 같은 컨트랙트, 외부 컨트랙트, 상속한 컨트랙트 내에서 호출 가능
External
- 함수가 외부 컨트랙트에서만 호출될 수 있음
- 같은 컨트랙트 내의 다른 함수에 의해 호출될 수 없음
- 위 특징을 제외하면 Public과 동일
내부 컨트랙트 | 외부 컨트랙트 | 상속 컨트랙트 | |
Private | O | X | X |
Internal | O | X | O |
Public | O | O | O |
External | X | O | X |
contract Sandwich {
uint private sandwichesEaten = 0;
function eat() internal {
sandwichesEaten++;
}
}
contract BLT is Sandwich {
uint private baconSandwichesEaten = 0;
function eatWithBacon() public returns (string) {
baconSandwichesEaten++;
// eat 함수가 internal로 선언되었기 때문에 여기서 호출이 가능하다
eat();
}
}
Interface
- 다른 컨트랙트와 상호 작용
- 소유하지 않은 컨트랙트와 소유한 컨트랙트가 상호 작용을 하려면 인터페이스를 정의해야 함
// 자신의 행운의 수를 저장할 수 있는 간단한 컨트랙트
contract LuckyNumber {
mapping(address => uint) numbers;
function setNum(uint _num) public {
numbers[msg.sender] = _num;
}
function getNum(address _myAddress) public view returns (uint) {
return numbers[_myAddress];
}
}
// LuckyNumber 컨트랙트의 인터페이스 정의 - 컨트랙트를 정의하는 것과 유사
contract NumberInterface {
// 다른 컨트랙트와 상호작용하고자 하는 함수만을 선언
function getNum(address _myAddress) public view returns (uint);
}
- 인터페이스 정의를 하는 것은 컨트랙트를 정의하는 것과 비슷함
- 다른 컨트랙트와 상호작용하고자 하는 함수만 선언, 함수 몸체는 정의하지 않음
if 문
- JS문과 동일
+ 솔리디티에서는 함수가 하나 이상의 값을 반환 할 수 있음
전체 코드
- zombiefactory.sol
pragma solidity ^0.4.19;
contract ZombieFactory {
// event 선언
event NewZombie(uint zombieId , string name, uint dna);
// dnaModulus라는 uint형 변수를 생성하고 10의 dnaDigits승을 배정
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
// Zombie 라는 구조체 생성
struct Zombie {
string name;
uint dna;
}
// Zombie 구조체의 public 배열 생성
Zombie[] public zombies;
// 매핑 생성
// 좀비 id로 좀비를 저장하고 검색
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
// 좀비 생성 private 함수 선언
// 함수 접근 제어자를 private에서 internal로 변경
function _createZombie(string _name, uint _dna) internal {
// 새로운 좀비를 zombies 배열에 추가
// 배열의 첫 원소가 0이라는 인덱스를 갖기 때문에, array.push() - 1은 막 추가된 좀비의 인덱스가 됨
uint id = zombies.push(Zombie(_name, _dna)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
// 새로운 좀비가 배열에 추가되면 이벤트 실행
NewZombie(id, _name, _dna);
}
// uint를 반환하는 view 함수 선언
function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
// 좀비 DNA가 16자리 숫자이기만을 원하므로 연산 후 값 반환
return rand % dnaModulus;
}
// public 함수 선언
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
// 좀비 dna 생성
uint randDna = _generateRandomDna(_name);
// 새로운 좀비 배열에 추가
_createZombie(_name, randDna);
}
}
- zombiefeeding.sol
pragma solidity ^0.4.19;
// zombiefactory.sol 불러오기
import "./zombiefactory.sol";
// 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
);
}
// ZombieFactory 상속
contract ZombieFeeding is ZombieFactory {
// 크립토키티 스마트 컨트랙트에서 데이터를 읽어 오도록 컨트랙트를 설정
address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
// `ckAddress`를 이용하여 kittyContract를 초기화
KittyInterface kittyContract = KittyInterface(ckAddress);
// 좀비 dna가 생명체의 dna와 혼합되어 새로운 좀비 생성
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
// 주인만 좀비에게 먹이를 줄 수 있음
require(msg.sender == zombieToOwner[_zombieId]);
// 먹이를 먹는 좀비의 dna 얻음
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus; // 16자리보다 크면 안됨
uint newDna = (myZombie + _targetDna) / 2; // 새로운 dna
// 키티 유전자를 가진 좀비는 dna 마지막 2자리를 99로 변경
if(keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
// 좀비 생성 함수 호출
// 인자로 좀비의 이름과 dna 값을 받음
_createZombie("NoName", newDna);
}
// 크립토키티 컨트랙트와 상호작용
// 크립토키티 컨트랙트에서 고양이 유전자를 얻어내는 함수를 생성
function feedOnKitty (uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
// dna가 혼합된 새로운 좀비를 생성하는 함수
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
github : https://github.com/dlwltn98/studyCryptoZombies
cryptoZombies: https://cryptozombies.io/ko/course
'Solidity' 카테고리의 다른 글
SolidityCryptoZombies 5 (ERC721 & 크립토 수집품) 정리 (1) | 2023.02.10 |
---|---|
SolidityCryptoZombies 4 (좀비 전투 시스템) 정리 (0) | 2023.02.09 |
CryptoZombies 3 (고급 솔리디티 개념) 정리 (0) | 2023.02.08 |
CryptoZombies 1 (좀비 공장 만들기) 정리 (0) | 2023.02.06 |
vscode + solidity (2) | 2023.02.05 |