본문 바로가기
프로그래밍/solidity

크립토좀비) 챕터2 개념과 sol 0.8.15버전으로 개선한 코드

by 한코코 2022. 5. 31.

mapping & address

이더의 잔액을 가진 계정들로 이루어딘 이더리움 블록체인.

각 계정은 계좌번호와 같은 주소를 가지고 있음 ( 계정을 가르키는 고유 식별자 )

주소는 특정 유저나 스마트 컨트랙트가 소유한다.

 

구조화된 데이트를 저장하는 방법으로 key-value(키-값)으로 짝지어진 저장소라 할 수 있다.

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

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

 

 

 

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) {
  // 저장된 _myNumber을 가져온다.
  // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다
  return favoriteNumber[msg.sender];
}

 

 

 

require

특정 조건이 참이 아닐때 함수가 에러메세지를 발생하고 실행을 멈춘다.

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

 

 

 

상속(is)

같은 함수를 반복하지 않도록 자식이 부모함수에게 접근해서 코드를 가져오는것을 말한다.

 

여기서 부모는 Doge, 자식은 BabyDoge.

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

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

 

 

 

import

파일을 불러오고싶을때 사용하면 컴파일러가 그 파일을 불러옴

코드가 너무 길어질때나 기능별로 파일을 나누고 싶을때 사용한다.

import "./someothercontract.sol";
contract newContract is SomeOtherContract {}

 

 

 

storage vs memory

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

- memory : 임시저장

보통 솔리디티가 상태변수에 설정된건 storage로, 함수내에 선언된 변수는 memory로 자동저장해주지만,

구조체와 배열을 처리할때는 직접 써줘야한다.

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    Sandwich storage mySandwich = sandwiches[_index];
    mySandwich.status = "Eaten!";
    // 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. 

    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: 
    // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. 

    anotherSandwich.status = "Eaten!";
    // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 
    // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다.
    
    sandwiches[_index + 1] = anotherSandwich;
    // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
  }
}

 

 

 

함수 접근 제어자 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();
  }
}

 

 

 

인터페이스(interface) (^0.8.15 업데이트)

스마트 컨트랙트 내에서 정의되어야할 필수 요소를 명시하며 다음과 같은 규칙을 따른다.

  1. 모든 함수는 external로 표시
  2. enum, structs 사용가능
  3. 상태변수, 생성자(consturctor) 선언 불가
  4. 다른 interface로부터 상속받지 못함

contract 키워드를 사용하며 다른 컨트랙트와 상호작용하는 함수를 선언만 한다.

함수를 선언만 하기때문에 본문을 구현하지 않으며, 상호작용 하지 않는 함수를 사용하지 않는다.

NumberInterface는 다른 컨트랙트, 그 중에서 필요한 함수 getNum만 사용할때 인터페이스를 사용한다.

 

* ^0.8.15버전에서는 contract로 선언하지 않고 interface라고 선언한다.

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

// >> pragma solidity ^0.8.15;
interface NumberInterface {
  function getNum(address _myAddress) external 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);
  }
}

 

 

 

 

다수의 반환값 처리

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

 

 

 

 

생성자 (contructor)

초기값이라고 생각하면 편하다.

컨트랙트와 동일한 이름을 가진 특별한 함수(생략도 가능)이며 컨트랙트가 배포될때 생성자가 딱 한번 실행된다.

생성자가 실행된 후에 컨트랙트의 최종코드가 생성되며, 이 코드가 블록체인에 저장된다.

최종코드에는 생성자코드와 생성자에서만 호출되는 함수는 포함되지 않는다.

전체 코드에서 1개의 생성자만 작성가능하며 직접 작성하지 않으면 자동으로 기본생성자가 사용된다.

가시성(visibility)는 internal 또는 public을 사용해야 한다.

internal일 경우 추상(abstract) 계약서이므로 단독으로 사용불가능하며 다른 계약서에서 상속받아 사용해야한다.

 

댓글