일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자료구조 #알고리즘
- vp #vc #did #신원인증 #블록체인
- #경제상식 #화폐 #금융 #화폐금융론 #경제학 #경제기본 #경제지식 #경제근육 #투자지식 #경제공부 #경제학전공 #금융이란 #화폐란 #금융시장 #금융시장역할 #화폐역할 #화폐역기능 #금융역기능 #
- 블록체인 #layer2 #레이어2 #이더리움스케일링
- 미쉬킨의화폐와금융 #미쉬킨 #화폐금융론 #화폐와금융 #경제학 #교양 #경제지식 #경제공부
- html #js #parsing
- #국제채권시장 #유로본드 #유로커런시 #유로달러 #외국채 #금융중개기관 #간접금융 #거래비용#다우존스공업평균지수 #나스닥종합지수 #FTSE100 #DAX #CAC40 #straittimes #항생지수 #거래비용 #유동성 #위
- 페이스북유니버시티 #마케팅교육 #마케팅캠프
- Today
- Total
평행우주 : world 1
[크립토좀비] Solidity Path : 2. 좀비가 희생물을 공격하다 본문
[크립토좀비] Solidity Path : 2. 좀비가 희생물을 공격하다
parallelworlds 2022. 2. 19. 03:36
매핑과 주소
데이터베이스에 저장된 좀비들에게 주인을 설정하고 게임을 멀티 플레이어 게임으로 만들기 위해 mapping과 address 자료형을 이용한다.
주소
이더리움 블록체인은 은행 계좌와 같은 계정들로 이루어져 있다.
계정은 이더리움 블록체인상의 통화인 _이더_의 잔액을 가지고,
계정을 통해 다른 계정과 이더를 주고 받을 수 있다.
각 계정은 주소를 가지고 있고, 주소는 특정 계정을 가리키는 고유 식별자이다.
0x0cE446255506E92DF41614C46F1d6df9Cc969183
"주소는 특정 유저(혹은 스마트 컨트랙트)가 소유한다"
따라서 주소를 좀비들에 대한 소유권을 나타내는 고유 ID로 활용할 수 있다.
유저가 우리 앱을 통해 새로운 좀비를 생성하면,
좀비를 생성하는 함수를 호출한 이더리움 주소에 그 좀비에 대한 소유권을 부여해보자
매핑
_매핑_은 솔리디티에서 구조화된 데이터를 저장하는 방법 중 하나
// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다:
mapping (address => uint) public accountBalance;
// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다
mapping (uint => string) userIdToName;
매핑은 기본적으로 키-값 (key-value) 저장소로, 데이터를 저장하고 검색하는 데 이용된다.
첫번째 예시에서 키는 address이고 값은 uint이다. 두번째 예시에서 키는 uint이고 값은 string이다.
Msg.sender
좀비 소유자를 추적하는 매핑을 가지고 있으니 _createZombie 메소드를 업데이트해서 이 매핑을 이용하고자 할 때, msg.sender 사용
msg.sender
솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 존재한다.
그 중 하나가 현재 함수를 호출한 사람 (혹은 스마트 컨트랙트)의 주소를 가리키는 msg.sender이다.
참고: 솔리디티에서 함수 실행은 항상 외부 호출자가 시작한다. 컨트랙트는 누군가가 컨트랙트의 함수를 호출할 때까지 블록체인 상에서 아무 동작을 안 한다. 그러니 항상 msg.sender가 있어야 한다.
msg.sender 이용하고 mapping 업데이트 예시
mapping (address => uint) favoriteNumber;
function setMyNumber(uint _myNumber) public {
// `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트한다 `
favoriteNumber[msg.sender] = _myNumber;
// ^ 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일하다
}
function whatIsMyNumber() public view returns (uint) {
// sender의 주소에 저장된 값을 불러온다
// sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다
return favoriteNumber[msg.sender];
}
누구나 setMyNumber을 호출하여 본인의 주소와 연결된 우리 컨트랙트 내에 uint를 저장할 수 있게 된다.
msg.sender를 활용해 이더리움 블록체인의 보안성을 이용할 수 있다.
누군가 다른 사람의 데이터를 변경하려면 ,해당 이더리움 주소와 관련된 개인키를 훔치는 것만이 유일한 방법이 된다
Require
각 플레이어가 새로운 플레이어들이 게임을 처음 시작할 때,
좀비 군대를 구성할 첫 좀비를 생성하기 위한 createRandomZombie함수를 한 번만 호출할 수 있도록 require를 활용해 만들어 보자.
require를 활용하면 특정 조건이 참이 아닐 때 함수가 에러 메시지를 보내고 실행을 멈춘다:
function sayHiToVitalik(string _name) public returns (string) {
// _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다
// (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에
// 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다)
require(keccak256(_name) == keccak256("Vitalik"));
// 참이면 함수 실행을 진행한다:
return "Hi!";
}
sayHiToVitalik("Vitalik")로 이 함수를 실행하면 "Hi!"가 반환된다.
"Vitalik"이 아닌 다른 값으로 이 함수를 호출할 경우, 에러 메시지가 뜨고 함수는 실행되지 않는다.
require는 함수를 실행하기 전에 참이어야 하는 특정 조건을 확인하는 데 유용하다.
상속
엄청나게 긴 컨트랙트 하나를 만들기 보다는 코드를 잘 정리해서 여러 컨트랙트에 코드 로직을 나누는 것이 합리적인 경우가 많다.
이를 보다 관리하기 쉽도록 하는 솔리디티 기능이 바로 컨트랙트 _상속_이다.
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
위 코드에서 BabyDoge 컨트랙트는 Doge 컨트랙트를 상속한다.
즉, BabyDoge 컨트랙트를 컴파일해서 구축할 때,
BabyDoge 컨트랙트는 catchphrase() 함수와 anotherCatchphrase() 함수에 모두 접근할 수 있다.
(Doge 컨트랙트에 정의되는 다른 어떤 public 함수가 정의되어도 접근 가능)
상속 개념은 "고양이는 동물이다"의 경우처럼 부분집합 클래스가 있을 때 논리적 상속을 위해 활용할 수 있다.
또한, 동일한 로직을 다수의 클래스로 분할해서 단순히 코드를 정리할 때도 활용 가능하다
Import
코드가 길어질 때, 여러 파일로 나누어 정리하면 관리하기 더 편해진다.
다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때, 솔리디티는 import라는 키워드를 이용한다:
import "./someothercontract.sol";
contract newContract is SomeOtherContract {
}
이 컨트랙트와 동일한 폴더에 (이게 ./가 의미하는 바임) someothercontract.sol이라는 파일이 있을 때,
이 파일을 컴파일러가 불러오게 된다
Storage vs Memory
솔리디티에는 변수를 저장할 수 있는 공간으로 storage와 memory 두 가지가 존재한다
Storage는 블록체인 상에 영구적으로 저장되는 변수를 의미한다.
Memory는 임시적으로 저장되는 변수로, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워진다
두 변수는 각각 컴퓨터 하드 디스크와 RAM와 비슷하다.
상태 변수(함수 외부에 선언된 변수)는 초기 설정상 storage로 선언되어 블록체인에 영구적으로 저장되는 반면,
함수 내에 선언된 변수는 memory로 자동 선언되어서 함수 호출이 종료되면 사라지지.
보통은 솔리디티가 자동으로 처리해 주지만,
함수 내의 구조체와 _배열_을 처리할 때는 이 키워드를 사용해야 한다
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;
function eatSandwich(uint _index) public {
// Sandwich mySandwich = sandwiches[_index];
// ^ 꽤 간단해 보이나, 솔리디티는 여기서
// `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다.
// 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다:
Sandwich storage mySandwich = sandwiches[_index];
// ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다.
// 그리고
mySandwich.status = "Eaten!";
// ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다.
// 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다:
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다.
// 그리고
anotherSandwich.status = "Eaten!";
// ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로
// `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다:
sandwiches[_index + 1] = anotherSandwich;
// ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
}
}
좀비 DNA
feedAndMultiply 함수 작성
새로운 좀비의 DNA를 계산하는 공식 : 먹이를 먹는 좀비의 DNA와 먹이의 DNA의 평균 계산
function testDnaSplicing() public {
uint zombieDna = 2222222222222222;
uint targetDna = 4444444444444444;
uint newZombieDna = (zombieDna + targetDna) / 2;
// ^ 3333333333333333이 될 것이다
}
함수 접근 제어자 더 알아보기
Internal과 External
public과 private 이외에도 솔리디티에는 internal과 external이라는 함수 접근 제어자가 있다.
internal은 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다 점을 제외하면 private과 동일하다
external은 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수 없다는 점을 제외하면 public과 동일
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();
}
}
좀비가 무엇을 먹나요?
좀비에게 크립토키티를 먹이로 주려면 크립토키티 스마트 컨트랙트에서 키티 DNA를 읽어와야 한다.
이게 가능한 이유는 크립토키티 데이터가 블록체인 상에 공개적으로 저장되어 있기 때문이다.
참고로 단지 크립토키티 데이터를 읽어 올 뿐이고, 실제로 데이터를 지울 수는 없다
다른 컨트랙트와 상호작용하기
블록체인 상에 있으면서 소유하지 않은 컨트랙트와 나의 컨트랙트가 상호작용을 하려면 우선 인터페이스를 정의해야 한다.
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];
}
}
이 컨트랙트는 아무나 자신의 행운의 수를 저장할 수 있는 간단한 컨트랙트이고, 각자의 이더리움 주소와 연관있다
이 주소를 이용해서 누구나 그 사람의 행운의 수를 찾아 볼 수 있다
이제 getNum 함수를 이용하여 이 컨트랙트에 있는 데이터를 읽고자 하는 external 함수가 있다고 생각해보자.
먼저, LuckyNumber 컨트랙트의 인터페이스를 정의해준다
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
차이점이 있지만, 인터페이스를 정의하는 것이 컨트랙트를 정의하는 것과 유사하다는 걸 기억하자
먼저, 다른 컨트랙트와 상호작용하고자 하는 함수만을 선언할 뿐(이 경우, getNum이 바로 그러한 함수)
다른 함수나 상태 변수를 언급하지 않는다
다음으로, 함수 몸체를 정의하지 않는다. 중괄호 {, }를 쓰지 않고 함수 선언을 세미콜론(;)으로 끝낸다.
그러니 인터페이스는 컨트랙트 뼈대처럼 보이고, 컴파일러도 그렇게 인터페이스를 인식하게 된다
dapp 코드에 이런 인터페이스를 포함하면 컨트랙트는 다른 컨트랙트에 정의된 함수의 특성, 호출 방법, 예상되는 응답 내용에 대해 알 수 있다.
인터페이스 활용하기
인터페이스가 정의
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
컨트랙트에서 인터페이스 이용
contract MyContract {
address NumberInterfaceAddress = 0xab38...
// ^ 이더리움상의 FavoriteNumber 컨트랙트 주소이다
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress)
// 이제 `numberContract`는 다른 컨트랙트를 가리키고 있다.
function someFunction() public {
// 이제 `numberContract`가 가리키고 있는 컨트랙트에서 `getNum` 함수를 호출할 수 있다:
uint num = numberContract.getNum(msg.sender);
// ...그리고 여기서 `num`으로 무언가를 할 수 있다
}
}
이런 식으로 컨트랙트가 이더리움 블록체인상의 다른 어떤 컨트랙트와도 상호작용할 수 있게 된다.
물론 상호작용하는 함수가 public이나 external로 선언되어 있어야 한다.
다수의 반환값 처리하기
다수의 반환값 처리
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();
}
// 혹은 단 하나의 값에만 관심이 있을 경우:
function getLastReturnValue() external {
uint c;
// 다른 필드는 빈칸으로 놓기만 하면 된다:
(,,c) = multipleReturns();
}
If 문
솔리디티에서 if 문은 자바스크립트의 if 문과 동일하다:
function eatBLT(string sandwich) public {
// 스트링 간의 동일 여부를 판단하기 위해 keccak256 해시 함수를 이용해야 한다는 것을 기억하자
if (keccak256(sandwich) == keccak256("BLT")) {
eat();
}
}
자바스크립트를 활용한 구현
자바스크립트와 web3.js를 활용하여 컨트랙트와 상호작용하는 예시:
var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)
// 우리 좀비의 ID와 타겟 고양이 ID를 가지고 있다고 가정하면
let zombieId = 1;
let kittyId = 1;
// 크립토키티의 이미지를 얻기 위해 웹 API에 쿼리를 할 필요가 있다.
// 이 정보는 블록체인이 아닌 크립토키티 웹 서버에 저장되어 있다.
// 모든 것이 블록체인에 저장되어 있으면 서버가 다운되거나 크립토키티 API가 바뀌는 것이나
// 크립토키티 회사가 크립토좀비를 싫어해서 고양이 이미지를 로딩하는 걸 막는 등을 걱정할 필요가 없다 ;)
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
let imgUrl = data.image_url
// 이미지를 제시하기 위해 무언가를 한다
})
// 유저가 고양이를 클릭할 때:
$(".kittyImage").click(function(e) {
// 우리 컨트랙트의 `feedOnKitty` 메소드를 호출한다
ZombieFeeding.feedOnKitty(zombieId, kittyId)
})
// 우리의 컨트랙트에서 발생 가능한 NewZombie 이벤트에 귀를 기울여서 이벤트 발생 시 이벤트를 제시할 수 있도록 한다:
ZombieFactory.NewZombie(function(error, result) {
if (error) return
// 이 함수는 레슨 1에서와 같이 좀비를 제시한다:
generateZombie(result.zombieId, result.name, result.dna)
})
'텃밭 1 : BlockChain > Solidity' 카테고리의 다른 글
[Solidity] Faucet을 구현하는 솔리디티 컨트랙트 분석 (0) | 2022.03.27 |
---|---|
[Solidity] Data type (0) | 2022.03.25 |
[실습 | 블록체인] Mnemonic Wallet 개발하기 (0) | 2022.03.17 |
[크립토좀비] Solidity Path : 3. 고급 솔리디티 개념 (0) | 2022.02.22 |
[크립토좀비] Solidity Path : 1. 좀비 공장 만들기 (0) | 2022.02.15 |