[#2 Crypto Zombies] 좀비가 희생물을 공격하다

cat_dev·2021년 2월 18일
0

Solidity

목록 보기
2/9
post-thumbnail

✍🏻 코드 분석

매핑과 주소

주소

이더리움 블록체인은 은행 계좌와 같은 계정들로 이루어져 있다. 계정은 이더리움 블록체인상의 통화인 _이더_의 잔액을 가진다.
은행 계좌에서 다른 계좌로 돈을 송금할 수 있듯이, 계정을 통해 다른 계정과 이더를 주고 받을 수 있다.
각 계정은 은행 계좌 번호와 같은주소를 가지고 있다. 주소는 특정 계정을 가리키는 고유 식별자로, 아래와 같이 표현된다.

0x0cE446255506E92DF41614C46F1d6df9Cc969183

"주소는 특정 유저(혹은 스마트 컨트랙트)가 소유한다"라는 점이 핵심!
따라서, 주소를 자원에 대한 소유권을 나타내는 고유 ID로 활용할 수 있다.

매핑

솔리디티에서 구조화된 데이터를 저장하는 방법이다.
매핑은 다음과 같이 사용할 수 있다.

//기본형
mapping(key => value) mapping_name;

// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다.
mapping (address => uint) public accountBalance; 

// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다.
mapping (uint => string) userIdToName;

매핑은 기본적으로 키-값 (key-value) 저장소로, 데이터를 저장하고 검색하는 데 이용된다.
첫번째 예시에서 키는address이고 값은uint이다. 두번째 예시에서 키는uint이고 값은string이다.

Msg.sender

솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 있다.
그 중의 하나가 현재 함수를 호출한 사람 (혹은 스마트 컨트랙트)의 주소를 가리키는msg.sender!

참고: 솔리디티에서 함수 실행은 항상 외부 호출자가 시작한다. 컨트랙트는 누군가가 컨트랙트의 함수를 호출할 때까지 블록체인 상에서 아무 것도 안 하고 있을 것이니 항상msg.sender가 있어야 한다.

Mapping을 아래와 같이 msg.sender가 이용할 수 있다.

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];
}

Key값을 []안에 넣고, value를 할당하는 형식으로 작동한다!
msg.sender는 현재 함수를 호출한 사람의 주소를 담고 있으므로 mapping의 Key인 address형이 된다.

require

특정 조건이 참이 아닐 때 함수가 에러 메세지를 발생하고 실행을 멈추게 한다.
보통 한 노드가 함수를 한 번 호출하는 데에 쓰임!

function sayHiToVitalik(string _name) public returns (string) {
  // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다
  // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 
  // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 참이면 함수 실행을 진행한다:
  return "Hi!";
}
 require(ownerZombieCount[msg.sender]==0);

이런 구문이 있으면 저 ownerZombieCount의 값이 0이 아닐 경우 에러 메세지가 출력되고, 함수가 작동되지 않는다.

상속

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

문법 진짜 이상하다.. 저 is 가 implements와 같은 기능을 함!
위 코드에 따르면 baby doge는 doge의 모든 public 함수 접근 가능!

저장 공간

storage == HDD

블록체인 상에 영구적으로 저장되는 변수
상태 변수(함수 외부 선언 변수)는 초기 설정상 storage로 선언됨
포인터로 사용되는 변수를 선언한다!

memory == RAM

임시적으로 저장되는 변수, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워짐
함수 내에 선언된 변수는 자동으로 Memory로 선언됨 
단순히 temp 변수로 사용된다!

💡 두개 지정은 구조체와 배열을 처리할 때 사용! 평소에는 솔리디티가 자동으로 해준다.

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;
    // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
  }
}

상속 관계 함수 접근

private를 internal로 바꾸어 선언하면 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근 가능하다!

다른 컨트랙트와 상호작용하기 - 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];
  }
}

이 컨트랙트는 아무나 자신의 행운의 수를 저장할 수 있는 간단한 컨트랙트이고, 각자의 이더리움 주소와 연관이 있을 것이다. 이 주소를 이용해서 누구나 그 사람의 행운의 수를 찾아 볼 수 있게 public으로 모든 함수가 정의되어있다.

이제getNum함수를 이용하여 이 컨트랙트에 있는 데이터를 읽고자 하는 external 함수가 있다고 해 보자. 먼저,LuckyNumber 컨트랙트의 인터페이스를 정의할 필요가 있다.

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

인터페이스는 자바 인터페이스 정의하듯이 하면 된다! 시작이 interface가 아니라 contract!!!!!
여기서는 인터페이스를 정의하는 것이 컨트랙트를 정의하는 것과 유사하다는 걸 참고해야 한다.
먼저, 다른 컨트랙트와 상호작용하고자 하는 함수만을 선언할 뿐 다른 함수나 상태 변수를 언급하지 않는다.
다음으로, 함수 몸체를 정의하지 않는다. 중괄호{,}를 쓰지 않고 함수 선언을 세미콜론(;)으로 간단하게 끝낸다.
그러니 인터페이스는 컨트랙트 뼈대처럼 보인다고 할 수 있다. 
우리의 dapp 코드에 이런 인터페이스를 포함하면 컨트랙트는 다른 컨트랙트에 정의된 함수의 특성, 호출 방법, 예상되는 응답 내용에 대해 알 수 있게 된다.

다수의 반환값 처리

또 참고하면 좋은게, 다른 프밍 언어들과 다르게 솔리디티는 함수가 하나 이상의 값을 반환할 수 있다!

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();
}

아래처럼 다수 값을 할당해서 처리! 하나의 값만 가져오는 것도 흥미로움.,,,네네

인터페이스 이용

//인터페이스 정의
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`으로 무언가를 할 수 있다
  }
}

코드 가운데쪽에 보면 인터페이스 numberContract를 선언해 사용한다는걸 알 수 있음! 초기값은 오른쪽 코드처럼 설정한다.

//getkitty는 10개 값을 반환하고, 맨 마지막에 있는 값을 kittyDna에 저장하는 코드

function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna);
  }

getkitty는 10개 값을 반환하고, 맨 마지막에 있는 값을 kittyDna에 저장하는 코드이다. 쉼표 진짜 어이없음..

string 동일 여부 판단

 if(keccak256(_species)==keccak256("kitty")){
        newDna = newDna - newDna % 100 + 99;
    }

hash값 비교로 스트링이 한글자한글자 일치하는지 판단 가능! 해시는 입력 값이 조금만 바뀌어도 확 바뀌니까..

profile
devlog

0개의 댓글