Course 2

학미새🐥·2022년 7월 31일
0

2

mapping과 address라는 자료형 알아보기

주소

  • 특정 유저나 스마트 컨트랙트가 가지는 고유한 지갑 주소

매핑

  • 솔리디티에서 구조화된 데이터를 저장하는 방식의 자료형 (like 구조체와 배열)
  • 키-값 저장소
  • 데이터를 저장하고 검색하는데에 사용됨
  • ex) 키 : 주소, 값 : 잔액
    mapping (address => uint) accountBalance;
  • ex) 키 : ID, 값 : 유저 이름
    mapping (uint => string) userIdtoName;

1.zombieToOwner라는 매핑을 생성한다. 키는 uint이고 (좀비 ID로 좀비를 저장하고 검색할 것이다), 값은 address이다. 이 매핑을 public으로 설정하자.
2.ownerZombieCount라는 매핑을 생성한다. 키는 address이고 값은 uint이다.

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;
  • 데이터 저장 시 : 매핑명[키] = 값 like 배열에 값 넣듯!

3

msg.sender

  • 솔리디티의 모든 함수에서 이용 가능한 전역 변수
  • 현재 함수를 호출한 (사용자나 스마트컨트랙트의) 주소를 가리킴

1.먼저, 새로운 좀비의 id가 반환된 후에 zombieToOwner 매핑을 업데이트하여 id에 대하여 msg.sender가 저장되도록 해보자.
2.그 다음, 저장된 msg.sender을 고려하여 ownerZombieCount를 증가시키자.

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

Chapter 4

각 유저마다 특정 함수를 한번씩만 호출할 수 있도록 제약을 거는 방법은?

require

  • 특정 조건이 거짓이 될 때 에러 메시지와 함께 함수 실행이 멈춘다.
  • 즉, if (false) { break; } 느낌!
  • 따라서! 함수 내부를 실행하기 이전에 참이어야만 하는 조건을 확인 하는 데에 사용된다

🔊 솔리디티는 string 비교 기능이 없어서 두 string의 keccak256 해시값을 비교하여 두 문자열이 같은지를 판단한다.

ex) require(keccak256(_name) == keccak256("Vitalik"));

require 키워드를 createRandomZombie 앞부분에 입력한다. require 함수가 ownerZombieCount[msg.sender]이 0과 같은지 확인하도록 하고, 0이 아닌 경우 에러 메시지를 출력하도록 한다.

    function createRandomZombie(string _name) public {
        // 여기서 시작
        require(ownerZombieCount[msg.sender]==0);
        
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

5

  • contract A가 contract B를 상속할 때 :
  • contract A is B { ... }
  • 컨트랙트 A는 컨트랙트 B의 함수까지 접근 가능

ZombieFactory 아래에 ZombieFeeding 컨트랙트를 생성한다. 이 컨트랙트는 ZombieFactory를 상속해야 한다.

contract ZombieFeeding is ZombieFactory {
    
}

6

  • 긴 코드가 여러 파일로 나누어 정리될 경우
  • 어떤 파일을 다른 파일로 불러오고 싶을 때 import!
  • ex) contract A가 다른 파일에 있는 contract B를 상속받으려면?
    import "./B.sol";
    contract A is B { }

새로운 파일 zombiefeeding.sol에 zombiefactory.sol를 불러 온다(import).

import "./zombiefactory.sol";

7

  • 솔리디티에서 변수를 저장하는 공간 : strage & memory

  • storage : 블록체인 상에 영구적으로 저장되는 변수

  • memory : 임시적으로 저장되는 변수

  • 함수 외부에서 선언된 변수 (상태 변수) : storage로 자동 선언

  • 함수 내부에서 선언된 변수 : memory로 자동 선언되어 함수 종료 시 사라짐

  • 평소에는 솔리디티가 자동으로 선언하지만, 구조체배열을 함수 내에서 처리할 때 키워드를 사용해야 함

  • 구조체의 값을 아예 영구적으로 변경하고자 할 때 -> 구조체 storage 변수명

  • 구조체를 복사해서 사용하고자 할 때 -> 구조체 memory 변수명

1.feedAndMultiply라는 함수를 생성한다. 이 함수는 uint형인 _zombieId 및 _targetDna을 전달받는다. 이 함수는 public으로 선언되어야 한다.
2.다른 누군가가 우리 좀비에게 먹이를 주는 것을 원치 않는다. 그러므로 주인만이 좀비에게 먹이를 줄 수 있도록 한다. require 구문을 추가하여 msg.sender가 좀비 주인과 동일하도록 한다. (이는 createRandomZombie 함수에서 쓰인 방법과 동일하다)
참고: 다시 말하지만, 우리가 작성한 확인 기능은 기초적이기 때문에 컴파일러는 msg.sender가 먼저 나올 것을 기대하고, 항의 순서를 바꾸면 잘못된 값이 입력되었다고 할 걸세. 하지만 보통 코드를 작성할 때 항의 순서는 자네가 원하는 대로 정하면 되네. 어떤 경우든 참이 되거든.
3.먹이를 먹는 좀비 DNA를 얻을 필요가 있으므로, 그 다음으로 myZombie라는 Zombie형 변수를 선언한다 (이는 storage 포인터가 될 것이다). 이 변수에 zombies 배열의 _zombieId 인덱스가 가진 값에 부여한다.

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
      require( msg.sender == zombieToOwner[_zombieId]);
      Zombie storage myZombie = zombies[_zombieId];
  }

8

1.먼저, _targetDna가 16자리보다 크지 않도록 해야 한다. 이를 위해, _targetDna를 _targetDna % dnaModulus와 같도록 해서 마지막 16자리 수만 취하도록 한다.
2.그 다음, 함수가 newDna라는 uint를 선언하고 myZombie의 DNA와 _targetDna의 평균 값을 부여해야 한다. (위의 예시 참고)
참고: myZombie.name와 myZombie.dna를 이용하여 myZombie 구조체의 변수에 접근할 수 있지.
3.새로운 DNA 값을 얻게 되면 _createZombie 함수를 호출한다. 이 함수를 호출하는 데 필요한 인자 값을 zombiefactory.sol 탭에서 확인할 수 있다. 참고로, 이 함수는 좀비의 이름을 인자 값으로 필요로 한다. 그러니 새로운 좀비의 이름을 현재로서는 "NoName"으로 하도록 하자. 나중에 좀비 이름을 변경하는 함수를 작성할 수 있을 것이다.

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    // 여기서 시작
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

9

  • private 함수는 상속하는 컨트랙트여도 함수에 접근할 수 없다.

Internal, External

  • internal : private과 유사하지만, internal 함수가 정의된 컨트랙트를 상속받는 컨트랙트 내에서는 접근이 가능함
  • external : public과 유사하지만, external 함수가 정의된 컨트랙트 바깥에서만 호출이 가능하고, 정의된 컨트랙트 내의 다른 함수에서는 호출될 수 없음

_createZombie() 함수를 private에서 internal로 바꾸어 선언하여 이 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근 가능하도록 한다.

function _createZombie(string _name, uint _dna) internal { }

10

  • 크립토 키티의 DNA는 블록체인 상에 공개 저장 되어있어서 이를 읽어와 사용할 수 있음

다른 컨트랙트와 상호작용

  • 블록체인 상의 다른 컨트랙트와 상호작용하기 위해 인터페이스를 정의
  • 인터페이스 정의는 컨트랙트 정의와도 살짝 유사
  • 다른 컨트랙트와 상호작용하고자 하는 함수만 선언
    -사용할 함수의 내부는 정의하지 않음. 즉, { }가 없고, 선언에서 끝남

🔊 솔리디티는 함수가 여러 값을 반환할 수 있음 (JS와 달리)

1.KittyInterface라는 인터페이스를 정의한다. 인터페이스 정의가 contract 키워드를 이용하여 새로운 컨트랙트를 생성하는 것과 같다는 점을 기억할 것.
2.인터페이스 내에 getKitty 함수를 선언한다 (위의 함수에서 중괄호 안의 모든 내용은 제외하고 return 키워드 및 반환 값 종류까지만 복사/붙여넣기 하고 그 다음에 세미콜론을 넣어야 한다).


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

  • 상호작용할 함수가 public이나 external일 경우 이렇게 다른 컨트랙트간의 상호작용 가능

    코드를 보면 ckAddress라는 변수에 크립토키티 컨트랙트 주소가 입력되어 있다. 다음 줄에 kittyContract라는 KittyInterface를 생성하고, 위의 numberContract 선언 시와 동일하게 ckAddress를 이용하여 초기화한다.

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

}

12

  • 여러 개의 값 반환
  // 여기에 함수를 정의 
  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna);
  }

13

  • 16자리의 DNA 에서 마지막 2자리를 통해 특별한 특성 만들기

if문

  • JS if문과 동일

    1.먼저, feedAndMultiply 함수 정의를 변경하여 _species라는 string을 세번째 인자 값으로 전달받도록 한다.
    2.그 다음, 새로운 좀비 DNA를 계산한 후에 if 문을 추가하여 _species와 "kitty" 스트링 각각의 keccak256 해시값을 비교하도록 한다.
    3.if 문 내에서 DNA 마지막 2자리를 99로 대체하고자 한다. 한가지 방법은 newDna = newDna - newDna % 100 + 99; 로직을 이용하는 것이다.
    설명: newDna가 334455라고 하면 newDna % 100는 55이고, 따라서 newDna - newDna % 100는 334400이다. 마지막으로 여기에 99를 더하면 334499를 얻게 된다.
    4.마지막으로, feedOnKitty 함수 내에서 이뤄지는 함수 호출을 변경해야 한다. feedAndMultiply가 호출될 때, "kitty"를 마지막 인자값으로 전달한다

  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; //dna 마지막 두자리 99로 만들기
    }
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    // 여기에 있는 함수 호출을 변경: 
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

14

  • js로 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)
})
profile
뭐든 다해보려는 공대생입니다

0개의 댓글