// 에드센스

토큰이란 몇몇 공통 규약을 따르는 스마트 컨트랙트다. 공통 함수집합이 있다. transfer()나 balanceOf()같은

ERC20토큰들이 똑같은 함수 집합을 공유하기에 이 토큰들은 똑같은 방식으로 상호작용 가능하다

내가 ERC20 토큰과 상호작용하는 앱을 만들면 모든 ERC20 토큰과 상호작용이 된다.

 

이것의 한가지 예시로는 거래소가 있다.

ERC20 토큰을 상장할때 실제로는 거래소에서 통신 가능한 또 하나의 스마트 컨트랙을 추가하는 것이다.

 

ERC20은 교체 가능

ERC721은 교체 불가능

contract SatoshiNakamoto is NickSzabo, HalFinney {
  // 이렇게 다중 상속 가능
}

 

 

 

전송

ERC721 스펙에서는 토큰을 전송할 때 2개의 다른 방식이 있다.

function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
  1. 첫 번째 방법은 토큰의 소유자가 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는 것.
  2. 두 번째 방법은 토큰의 소유자가 먼저 위에서 본 정보들을 가지고 approve를 호출하는 것. 그리고서 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장한다. 보통 mapping (uint256 => address)를 쓴다. 이후 누군가 takeOwnership을 호출하면, 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인한다. 그리고 허가를 받았다면 해당 토큰을 그에게 전송한다.

transfer와 takeOwnership 모두 동일한 전송 로직을 가지고 있다.

순서만 반대인 것(전자는 토큰을 보내는 사람이 함수를 호출하고, 후자는 토큰을 받는 사람이 호출하는 것).

 

 

 

safeMath

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

assert는 조건을 만족하지 않으면 에러를 발생시킨다는 점에서 require와 비슷하다. assert와 require의 차이점은, require는 함수 실행이 실패하면 남은 가스를 사용자에게 되돌려 주지만, assert는 그렇지 않다는 것이다. assert는 일반적으로 코드가 심각하게 잘못 실행될 때 사용하네(like, uint 오버플로우의 경우)

 

 

 

 

 

 

https://cryptozombies.io/ko/course

 

#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts

cryptozombies.io

 

 

 

'Block Chain' 카테고리의 다른 글

[BlockChain] 크립토좀비 레슨4  (0) 2023.02.14
[BlockChain] 크립토좀비 레슨3  (0) 2023.02.13
[BlockChain] 크립토좀비 레슨2  (0) 2023.02.11
[BlockChain] 크립토좀비 레슨1  (0) 2023.02.10
[BlockChain] Move 언어  (0) 2023.02.03

payable

payable 함수는 이더를 받을 수 있게하는 특별한 함수 유형.

일반적인 웹 서버에서 API 함수를 실행할 때에는, 우리는 함수 호출을 통해서 US 달러를 보낼 수 없다. 물론 비트코인도.

하지만 이더리움에서는, 돈(이더), 데이터(transaction payload), 그리고 컨트랙트 코드 자체 모두 이더리움 위에 존재하기 때문에, 우리가 함수를 실행하는 동시에 컨트랙트에 돈을 지불하는 것이 가능하다.

contract OnlineStore {
  function buySomething() external payable {
    // 함수 실행에 0.001이더가 보내졌는지 확실히 하기 위해 확인:
    require(msg.value == 0.001 ether);
    // 보내졌다면, 함수를 호출한 자에게 디지털 아이템을 전달하기 위한 내용 구성:
    transferThing(msg.sender);
  }
}

여기서, msg.value는 컨트랙트로 이더가 얼마나 보내졌는지 확인하는 방법.

 

 

 

 

컨트랙트에서 이더를 꺼내는 방법

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

this.balance는 컨트랙트에 저장돼있는 전체 잔액을 반환

 

 

 

한 가지 활용법으로는 구매자와 판매자가 존재하는 컨트랙트에서, 판매자의 주소를 storage에 저장하고, 누군가 판매자의 아이템을 구매하면 구매자로부터 받은 요금을 그에게 전달할 수도 있다

seller.transfer(msg.value)

 

 

 

 


 

난수

keccak256을 통해 난수를 만들수있지만 이 방법은 정직하지 않은 노드의 공격에 취약하다.

 

우리가 이더리움에서 컨트랙트의 함수를 실행하게되면 이를 하나의 트랜잭션(transaction)으로서 네트워크의 노드 하나 혹은 여러 노드에 실행을 알리게 된다. 그 후 네트워크의 노드들은 여러 개의 트랜잭션을 모으고, "작업 증명"으로 알려진 계산이 매우 복잡한 수학적 문제를 먼저 풀기 위한 시도를 하게 된다. 그리고서 해당 트랜잭션 그룹을 그들의 작업 증명(PoW)과 함께 블록으로 네트워크에 배포하게 된다.

 

이것이 우리의 난수 함수를 취약하게 만든다

 

내가 만약 노드를 실행하고 있다면, 나는 오직 나의 노드에만 트랜잭션을 알리고 이것을 공유하지 않을 수 있다.

우리가 동전 던지기 컨트랙트를 사용한다고 할 때 해보자. 내가 동전을 던져서 당첨이 되지 않았다면 내가 풀고 있는 다음 블록에 해당 트랜잭션을 포함하지 않을 수 있다.  당첨이 된 경우에만 선택적으로 트랜잭션을 다음 블록에 추가시키면 된다.

이 짓을 무한대로 반복할 수 있기에 문제.

 

 

개선하는 방법은 oracle(이더리움 외부에서 데이터를 받아오는 안전한 방법 중 하나)을 사용해서 블록체인 밖에서 안전한 난수를 만드는 방법

 

 

 

 

https://cryptozombies.io/ko/course

 

#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts

cryptozombies.io

 

 

'Block Chain' 카테고리의 다른 글

[BlockChain] 크립토좀비 레슨5  (0) 2023.02.28
[BlockChain] 크립토좀비 레슨3  (0) 2023.02.13
[BlockChain] 크립토좀비 레슨2  (0) 2023.02.11
[BlockChain] 크립토좀비 레슨1  (0) 2023.02.10
[BlockChain] Move 언어  (0) 2023.02.03

Ownable 컨트랙트

오픈제플린 라이브러리의 Ownable 컨트랙트

 

즉, Ownable 컨트랙트를 상속시키면

  1. 컨트랙트가 생성되면 컨트랙트의 생성자가 owner에 msg.sender(컨트랙트를 배포한 사람)를 대입한다.
  2. 특정한 함수들에 대해서 오직 소유자만 접근할 수 있도록 제한 가능한 onlyOwner 제어자를 추가한다.
  3. 새로운 소유자에게 해당 컨트랙트의 소유권을 옮길 수 있도록 한다.

의 역할을 수행할 수 있다. (일단 여기서 사용하는것들은 이렇다)

 

 

 


함수 제어자

함수 제어자는 함수처럼 보이지만, function 키워드 대신 modifier 키워드를 사용.

함수를 호출하듯이 직접 호출할 수는 없다.

함수 정의부 끝에 해당 함수의 작동 방식을 바꾸도록 제어자의 이름을 붙일 수 있다.

 

/**
 * @dev Throws if called by any account other than the owner.
 */
modifier onlyOwner() {
  require(msg.sender == owner);
  _;
}

이렇게 생김

 

 

contract MyContract is Ownable {
  event LaughManiacally(string laughter);

  // 아래 `onlyOwner`의 사용 방법을 잘 보게:
  function likeABoss() external onlyOwner {
    LaughManiacally("Muahahahaha");
  }
}

likeABoss 함수를 호출하면, onlyOwner의 코드가 먼저 실행된다. 그리고 onlyOwner의 _; 부분을 likeABoss 함수로 되돌아가 해당 코드를 실행하게 된다.

 

제어자를 사용할 수 있는 다양한 방법이 있지만, 가장 일반적으로 쓰는 예시 중 하나는 함수 실행 전에 require 체크를 넣는 것.

onlyOwner의 경우에는, 함수에 이 제어자를 추가하면 오직 컨트랙트의 소유자(배포한 사람)만이 해당 함수를 호출할 수 있다.

 

함수에 onlyOwner 키워드를 붙혀서 컨트랙트 소유자인지 확인하고 소유자인 경우에만 허용한다

--> 사용자들이 우리 컨트랙트를 마구 수정하지 못하게 하면서도 우리 디앱의 핵심적인 부분을 업데이트할 수 있도록 하는 방법이다

 

 

 

 

 


가스

솔리디티에서 사용자들은 함수를 호출할때마다 가스를 사용한다

 

솔리디티에서는 uint의 크기에 상관없이 256비트의 저장 공간을 미리 잡아놓기 때문에 하위 타입들을 쓰는 것은 아무런 이득이 없다.

예를들어, uint(uint256) 대신에 uint8을 쓰는 것은 가스 소모를 줄이는 데에 아무 영향이 없다.

 

하지만 struct 안에서는 다르다.

만약 구조체 안에 여러 개의 uint를 만든다면, 가능한 더 작은 크기의 uint를 쓰는게 좋다.

 

struct NormalStruct {
  uint a;
  uint b;
  uint c;
}

struct MiniMe {
  uint32 a;
  uint32 b;
  uint c;
}

// `mini`는 구조체 압축을 했기 때문에 `normal`보다 가스를 조금 사용할 것이네.
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);

 

 

 

 


인수를 가지는 함수 제어자

함수 제어자는 사실 인수 또한 받을 수 있다.

// 사용자의 나이를 저장하기 위한 매핑
mapping (uint => uint) public age;

// 사용자가 특정 나이 이상인지 확인하는 제어자
modifier olderThan(uint _age, uint _userId) {
  require (age[_userId] >= _age);
  _;
}

// 차를 운전하기 위햐서는 16살 이상이어야 하네(적어도 미국에서는).
// `olderThan` 제어자를 인수와 함께 호출하려면 이렇게 하면 되네:
function driveCar(uint _userId) public olderThan(16, _userId) {
  // 필요한 함수 내용들
}

olderthan 제어자가 함수와 비슷하게 인수를 받는 것을 볼 수 있다. 그리고 driveCar 함수는 받은 인수를 제어자로 전달하고 있다.

 

 

 

 


View 함수는 가스를 소모하지 않는다

view 함수는 사용자에 의해 외부에서 호출되었을 때 가스를 전혀 소모하지 않는다.

이건 view 함수가 블록체인 상에서 실제로 어떤 것도 수정하지 않기 때문 - 데이터를 읽기만 하지.

 

함수에 view 표시를 하는 것은 

 

"이 함수는 실행할 때 자네 로컬 이더리움 노드에 질의만 날리면 되고, 블록체인에 어떤 트랜잭션도 만들지 않아"

 

라고 web3.js에 이렇게 말하는 것과 같다.

(트랜잭션은 모든 개별 노드에서 실행되어야 하고, 가스를 소모한다).

 

 

참고: 만약 view 함수가 동일 컨트랙트 내에 있는, view 함수가 아닌 다른 함수에서 내부적으로 호출될 경우, 여전히 가스를 소모할 것이다. 이것은 다른 함수가 이더리움에 트랜잭션을 생성하고, 이는 모든 개별 노드에서 검증되어야 하기 때문.

그러니 view 함수는 외부에서 호출됐을 때에만 무료다.

 

 

(pure는 블록체인으로부터 어떤 데이터도 읽거나 쓰지 않는다는 뜻)

 


storage

솔리디티에서 storage는 비싸다

storage 대신 함수가 종료될때 사라지는 memory를 사용하는것도 하나의 방법

function getArray() external pure returns(uint[]) {
  // 메모리에 길이 3의 새로운 배열을 생성한다.
  uint[] memory values = new uint[](3);
  // 여기에 특정한 값들을 넣는다.
  values.push(1);
  values.push(2);
  values.push(3);
  // 해당 배열을 반환한다.
  return values;
}

 

 

 

 

 

https://cryptozombies.io/ko/course

 

#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts

cryptozombies.io

 

'Block Chain' 카테고리의 다른 글

[BlockChain] 크립토좀비 레슨5  (0) 2023.02.28
[BlockChain] 크립토좀비 레슨4  (0) 2023.02.14
[BlockChain] 크립토좀비 레슨2  (0) 2023.02.11
[BlockChain] 크립토좀비 레슨1  (0) 2023.02.10
[BlockChain] Move 언어  (0) 2023.02.03

매핑과 주소 자료형

 

msg.sender: 전역변수, 현재 함수를 호출한 사람의 주소를 반환함

 

require: 특정 조건 검사(이프문은 따로있다, require는 if+revert 느낌)

 

솔리디티는 상속이 된다

https://bloccat.tistory.com/35

 

솔리디티에는 변수를 저장할 수 있는 공간으로 storage와 memory가 있다

 

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

memory는 일시적으로 저장되는 변수

 

일반적으로 변수에 사용할때는 솔리디티가 기본적으로 

함수 외부에 선언되는 변수(상태변수)는 기본적으로 storage

함수 내부에 선언되는 변수는 기본적으로 memory -> 함수 호출 종료시 사라짐

으로 설정해준다

근데

구조체나 배열을 사용할때는 storage나 memory 키워드를 명시해야한다

 

 

다른 컨트랙트와 상호작용하려면 인터페이스를 정의해야한다

 

다음 코드는 크립토키티 컨트랙트의 getKitty 함수인데 얘랑 상호작용하려면 인터페이스를 정의해야한다

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
) {
    Kitty storage kit = kitties[_id];

    // if this variable is 0 then it's not gestating
    isGestating = (kit.siringWithId != 0);
    isReady = (kit.cooldownEndBlock <= block.number);
    cooldownIndex = uint256(kit.cooldownIndex);
    nextActionAt = uint256(kit.cooldownEndBlock);
    siringWithId = uint256(kit.siringWithId);
    birthTime = uint256(kit.birthTime);
    matronId = uint256(kit.matronId);
    sireId = uint256(kit.sireId);
    generation = uint256(kit.generation);
    genes = kit.genes;
}

 

 

인터페이스는 이렇게 생김

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

중괄호부분은 날리고 리턴값까지만 정의하고 중괄호부분에 세미콜론으로 마무리한다

 

 

 

이 인터페이스를 활용하려면

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다
  KittyInterface kittyContract = KittyInterface(ckAddress);
}
  function feedOnKitty(uint _zombieId, uint _kittyId) public {
      uint kittyDna 
      (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
  }

솔리디티는 리턴값이 여러개가 될 수 있기에 콤마를 저렇게 나열해서 getKitty 함수의 10번째 반환값인 genes만 뽑아왔다.

 

 

 

 

 

https://cryptozombies.io/ko/course

 

#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts

cryptozombies.io

 

다시 초심으로 돌아가 솔리디티부터 해볼려고한다.

걷지도 못하는데 어떻게 뛰겠는가!

그래서 크립토좀비를 한 챕터마다 정리하면서 진행해보려고했다만

첫 챕터는 그냥 완전 기본 문법(변수형 선언, 형변환, 구조체)이런것들이라 딱히 적을게 없긴하다.

 

 

 


 

 

 

그래도! 몇 가지 기존의 프로그래밍 언어와 조금 다른 부분을 정리해보자면

 

1. view

function sayHello() public view returns (string) {}

이런식으로 view라는 키워드를 쓴다.

얘는 솔리디티에서 상태를 변화시키지 않는다는 의미다.

즉, 어떤 값을 쓰거나 변경하지 않는다는 의미. only view만 하는 함수라는 의미.

 

-> storage를 읽을 수 있지만 변경은 안됨

 

 

2. pure

function _multiply(uint a, uint b) private pure returns (uint) {
  return a * b;
}

pure는 함수가 앱에서 어떤 데이터도 접근하지 않는것을 의미한다.

 

-> storage를 읽을수도 없고 변경도 안된다

 

 

3. 이벤트

// 이벤트를 선언한다
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  // 이벤트를 실행하여 앱에게 add 함수가 실행되었음을 알린다:
  IntegersAdded(_x, _y, result);
  return result;
}
YourContract.IntegersAdded(function(error, result) {
  // 결과와 관련된 행동을 취한다
})

이벤트는 나중에 더 깊게 다뤄볼 것이고 일단 챕터1에서는 간단히 보고 넘어간다.

컨트랙트는 특정 이벤트가 일어나는지 "귀를 기울이고" 그 이벤트가 발생하면 행동을 취한다.

 

사용자단의 자바스크립트 코드가 IntegerAdded 이벤트를 듣고있다가 컨트랙트에서 실행되면 행동을 취한다.

 

 

 

 

 

 

https://cryptozombies.io/ko/course

 

#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts

cryptozombies.io

 

무브랭이 뭐고 솔리디티보다 뭐가 나은걸까?

 

 

 

무브언어는 리브라 스마트 컨트랙 프로그래밍을 위해 개발된 언어다

 

리브라는 페이스북이 발행한 결제용 암호화폐 -> 현재는 명칭이 바뀌어 디엠(Diem)이다

 

페이스북은 왜 결제용 암호화폐를 만든걸까? -> 국가가 발행하는 화폐를 대체하고 은행 계좌가 없는 수십억명에게 지불 네트워크를 제공하기 위해ㅇㅇ -> 지갑 이름은 "캘리브라"

 

리브라는 주요국의 법정통화와 채권을 담보로하는 안정된 스테이블코인이라는 강점을 내세웠었다.

 

하지만 각국 규제 당국들은 리브라를 전통 통화에 대한 도전이라고 여기어 강한 규제를 맥여버렸고 리브라는 디엠이 되었다

 

즉, 무브언어는 디엠코인 스마트 컨트랙 프로그래밍을 위해 개발된 언어이다

 

 


 

 

솔리디티랑 뭐가 달라?

 

솔리디티도 많이 안해보았기에.. 체감되는 부분은 없고 글로만 이해해보자면

 

  • 블록체인에서 사용되는 기존 스크립트 언어들의 한계 -> 코인을 매개하는 과정상 단순한 정수형을 사용한다는 점. 코인, 토큰을 규정하는 자료타입이 없다. 근데 무브는 있다(고한다 아직안써봄ㅎㅎ)
  • 무브는 지금까지 발생했던 스마트 컨트랙 관련 사고들을 참고하여 설계되었고 개발자의 의도를 쉽고 정확하게 표현할 수 있기에 예기치 못한 버그를 줄일 수 있다(고 한다)
  • 디지털 실물 자산에 성격을 부여하여 디지털 자산의 주인을 한명으로 규정하고 한번밖에 사용될 수 없도록하며 동일한 자산의 생성을 제한하는 기능도 있다(고한다)
  • 선언된 데이터는 해당 리소스를 선언한 모듈에서만 변경, 삭제할 수 있다(고한다)
  • 무브언어에서 리소스 객체는 소모될 뿐 복사는 되지 않는다(고한다)

 

오늘의 강의는 뭘까~용


1. 스마트 컨트랙 코드는 실행할때 돈이 든다. 

왜?

나의 트랜잭션을 블록에 넣어줄 마이너는 땅파서 컴퓨터를 돌려주는게 아니기때문.

 

얼마나 들까?

밑에서 대~강 구해봤다.

 

왜 알아야하나?

알아야 나중에 코딩할때 더 값싼 방법으로 코딩할 수 있으니까

 

가스비는 일반적으로 gwei로 표시되며, 이는 ETH의 10억분의 1(0.000000001 ETH)이다

 

이더리움의 수수료

수수료 = gas(gasLimit) * gasPrice

 

예시)

수수료 = gas(21000) * gasPrice(1gwei)

수수료 = 21000000000000gwei

이때, 1ETH = 10 ** 18wei이니까

 

수수료 = 0.00021ETH 가 된다.

 

gas와 gasPrice는 다음과 같은 차이점이 있다.

  • gas(gasLimit) : 본 송금 '작업'에서 소비되는 가스량
    (estimated 한 수치라서 변경가능하다. 하지만 넘 작게하면 거부됨)
  • gasPrice : 내가 가스당 지불할 가격
    (경매처럼 내가 금액을 제안하는 것임)

 

MAX TOTAL : gas * gasPrice 로 형성된 최종지불금액

gasPrice를 높게 잡을수록 이더리움 블록체인에서 마이너들이 내 트랜잭션을 빠르게 채굴해준다.

 

 

 

 

확인해보자

이더스캔에서 아무 트랜잭션이나 한번 봐보자

 

Transaction Fee(0.000739157072325) = gas(21000) * gasPrice(0.000000035197955825)

임을 확인할 수 있다.

 

Gas Limit & Usage by Txn은 뭐냐?

Gas Limit(앞에서 말한 gas와 동일)은 내가 이 값 이상으로는 가스비를 내지 않을 것이다라는 최대한도

Usage는 실제로 사용된 가스비를 의미

이 예시의 경우는 Limit의 100%를 사용했다.

 

넉넉하게 Gas Limit을 주면 알아서 최적의 가스비로 지불이 된다.

너무 GasLimit을 낮게주면 트랜잭션이 실패되니 넉넉하게 주자

 

 

 

 


2. 우리가 작성한 스마트컨트랙이 사용할 Gas를 대~강 계산해보자

가스비가 많이드는 연산: 블록체인의 용량을 늘리는 연산(저장하는 연산)

 

 

참고로 한 블록에서 사용가능한 가스비가 정해져있기에(Block Gas Limit) 무제한으로 용량을 늘리는(정보를 저장하는) 연산을 할 수 없다.

 

이 블록안에 있는 모든 트랜잭션 들이 사용할 수 있는 gas limit은 30,029,295이다.

이 값을 초과할 정도의 연산은 블록에 들어갈 수 없다.

 

이더리움의 가스비는 다음과 같이 정해져있다

  • 32Bytes 저장에 20000gas가 든다
  • 32Bytes 기존 변수에 있는 값을 바꿀때 5000gas
  • 기존변수를 초기화해서 더 이상 쓰지 않을때 10000gas return

 

 

Lottery.sol의 가스비를 파헤쳐보자

우리가 만든 Bet함수를 실행할 때 발생하는 가스비를 계산해보자

truffle console을 통해서 Bet함수를 실행시켜보았고 90846gas가 발생했다

 

 

연속으로 한번 더 실행하면 75846gas가 발생한다.

왜 갑자기 적어졌지? 끝에서 설명하겠다.

 

 

 

 

일단 우리가 작성한 코드를 보자

 

  function bet(bytes challenges) public payable returns (bool result) {
    // 돈이 제대로 왔는지 확인
    // require는 if역할, msg.value는 컨트랙트가 받은금액, 문자열은 조건이 false때 출력할 문구
    require(msg.value == BET_AMOUNT, 'Not Enough ETH');

    // 큐에 베팅정보를 넣기
    require(pushBet(challenges), 'Fail to add a new Bew Info');

    // 이벤트 로그를 찍는다
    // (몇번째 배팅인지, 누가 배팅했는지, 얼마 배팅했는지, 어떤글자로 베팅했는지, 어떤 블록에 정답이 있는지)
    emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);
    return true;
  }

 

이러한 Bet함수에서 pushBet을 호출하는 부분이벤트를 emit하는 부분에서 가스비가 든다.

즉, 첫번째 발생한 90846가스로 계산을 시작해보면

90846 = 기본가스(21000) + pushBet(???가스) + emit(대략 5000gas)

 

 

자 그러면 pushBet함수를 보자

  function pushBet(bytes challenges) internal returns (bool) {
    BetInfo memory b; 
    b.bettor = msg.sender; // address형 변수 사용 (1)
    b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // uint256형 변수 사용 (2)
    b.challenges = challenges; // bytes형 변수 사용 (3)

    _bets[_tail] = b; // 그닥 크지 않다 (4)
    _tail++; // 32Byte형 변수 사용(5)

    return true;
  }

(1)번은 address형 변수를 사용했으므로 20Byte를 사용한 것

(3)번은 Byte형 변수를 사용했으므로 1Byte를 사용한 것

이더리움 블록체인에서 사용하는 기본저장단위는 32Byte다

 

즉, 20Byte를 사용했든 1Byte를 사용했든 둘 다 32Byte를 사용한 것이므로 64Byte를 사용한 것이 맞으나

작은 단위의 바이트에 대해서는 쪼개서(?) 연산해준다고 한다.

따라서 (1)번과 (3)번 합쳐서 32Byte를 사용한 셈 치면 된다.

(1) + (3) = 20000gas (32Byte)

 

(2)번은 uint256형 변수를 사용했으므로 32Byte 사용한 것

(2) = 20000gas (32Byte)

(4) = 무시

(5) = 20000gas (32Byte)

 

 

즉, pushBet에서 발생한 gas는 (1) + (2) + (3) + (4) + (5) = 60000gas

 

 

90846 = 기본가스(21000) + pushBet(60000gas가스) + emit(대략 5000gas)

90846 = 86000gas + 기타 자잘한 연산들

 

이라고 할 수 있다.

 

그러면 왜 2번째 실행했을때는 75846gas만 발생했느냐?

 

32Bytes 기존 변수에 있는 값을 바꿀때 5000gas가 들기때문이다.

  function pushBet(bytes challenges) internal returns (bool) {
    BetInfo memory b; 
    b.bettor = msg.sender; // 함수 호출한 사람
    b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; 
    b.challenges = challenges; 

    _bets[_tail] = b;
    _tail++; // 초기 0에서 1되는 것은 20000gas지만 1에서 2되는 것은 수정이므로 5000gas만 든다

    return true;
  }

즉 첫번째 실행보다 15000gas가 덜 발생한다.

90846 - 15000 = 75846

대강 맞는것을 확인할 수 있다.

 

므ㅏ

강의출처:


1. 사용할 컨트랙에 대한 정보를 가져온다.

contract("Lottery", ([deployer, user1, user2]) => {
  ...
});

트러플에서는 mocha와 chai를 스마트컨트랙에서 사용할 수 있도록 조절해 놓았다고 한다.

따라서 테스트를 시작할 때 contract 키워드로 시작하면 된다.

 

첫번째 인자로는 컨트랙트 이름이 들어가고,

두번째 인자로는 콜백함수가 들어간다. 이 콜백함수의 파라미터로 ganache-cli에서 생성되는 10개의 account가 들어간다.

즉 최대 10개의 인자를 넣을 수 있다.

 

위 코드처럼 3개를 썼다는 것은 

deployer에 0xd53....

user1에 0xd07....

user2에 0x040....

이렇게 3개의 주소가 파라미터로 들어간 것이다.

 

 

 

 


2. 스마트 컨트랙 배포

테스트를 하려면 스마트컨트랙 코드가 블록에 들어가서 실행되어야 하므로 컨트랙을 배포해주자

contract("Lottery", ([deployer, user1, user2]) => {
  beforeEach(async () => {
    console.log("Before each");
    lottery = await Lottery.new(); //테스트에서 사용할 스마트 컨트랙 배포
  });
});

 

 

 

 


3. 테스트코드 작성

  let betAmount = 5 * 10 ** 15;
  let BET_BLOCK_INTERVAL = 3;
  
  describe("Bet", async () => {
    it("베팅큐에 값이 잘 들어갔는지 확인하기", async () => {
      // 베팅한다
      const receipt = await lottery.bet("0xab", {
        from: user1,
        value: betAmount, // 5 * 10 ** 15 -> 0.005ETH
      });

      // pot머니(상금)은 아직 결과가 들어나지 않았기에 0이어야 한다.
      let pot = await lottery.getPot();
      assert.equal(pot, 0);

      // 컨트랙트 주소로 0.005이더가 들어왔는지 확인한다
      let contractBalance = await web3.eth.getBalance(lottery.address); // web3가 자동으로 주입돼있다
      assert.equal(contractBalance, betAmount);

      // 베팅인포가 제대로 들어갔는지 확인한다
      let currentBlockNumber = await web3.eth.getBlockNumber();
      let bet = await lottery.getBetInfo(0); // 큐 제일 앞의 베팅정보를 가져온다

      // 큐에 넣은 베팅정보가 올바른지 확인
      assert.equal(
        bet.answerBlockNumber,
        currentBlockNumber + BET_BLOCK_INTERVAL
      );
      assert.equal(bet.bettor, user1);
      assert.equal(bet.challenges, "0xab");

      // 로그(BET이라는 이벤트)가 제대로 찍혔는지 확인한다
      await expectEvent.inLogs(receipt.logs, "BET");
    });

    it("0.005이더가 안들어왔을때는 실패해야한다", async () => {
      // 트랜잭션 실패
      await assertRevert(
        // 두번째 인자는 트랜잭션 오브젝트, transaction object란
        // (chainId, value, to, from, gas(limit), gasPrice)
        lottery.bet("0xab", { from: user1, value: 4000000000000000 }) // bet함수 실행 -> 0.004ETH가 들어갔기에 트잭이 실패나야한다
      );
    });
  });

 

 

event, emit을 테스트하기

const assert = require("chai").assert;

// 직접 발생시킨 event의 로그가 있는지 확인한다.
const inLogs = async (logs, eventName) => {
  const event = logs.find((e) => e.event === eventName);
  assert.exists(event);
};

module.exports = {
  inLogs,
};

+ Recent posts