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

크립토좀비) 챕터3 개념

by 한코코 2022. 7. 6.

이더리움 네트워크에 컨트랙트를 배포하고나면 절대로 컨트랙트는 변하지 않는다.

 

 

 

컨트랙트를 소유하기 : OpenZeppelin의 Ownable 컨트랙트

  1. 컨트랙트가 생성되면 컨트랙트의 생성자가 owner에 컨트랙트를 배포한 사람을 대입함
  2. 특정한 함수들에 대해 오직 소유자만 접근하도록 onlyOwner 제어자 추가함
  3. 새로운 소유자에게 해당 컨트랙트의 소유권을 옮길 수 있도록 함.
  4. 생성자 - 컨트랙트와 동일한 이름을 가진, 생략할 수 있다. 함수가 실행될 때 한번만 실행된다.
contract Ownable {
  address public owner;
  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  // 생성자
  function Ownable() public {
    owner = msg.sender;
  }

  // 함수 제어자
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}

 

 

 

Ownable 함수 제어자

함수처럼 보이지만 function대신 modifier 키워드를 사용함.

함수 호출하듯 직접 호출할 수는 없고, 함수 정의부 끝에 해당 함수의 작동 방식을 바꾸도록 제어자의 이름을 붙인다.

likeABoss 함수를 호출하면 onlyOwner가 먼저 실행되고, _; 부분에서 likeABoss다시 돌아간다.

// 함수 제어자
modifier onlyOwner() {
  require(msg.sender == owner);
  _; // 호출했던 likeABoss로 돌아가 실행을 계속한다
}

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

  function likeABoss() external onlyOwner {
    LaughManiacally("Muahahahaha");
  }
}

 

 

 

가스 (Gas)

이더리움 네트워크는 EVM으로 이루어져있고, 사용자가 올린 DApp의 함수를 EVM 위에서 연산하게 된다.

프로그램을 돌리는데 얼마나 자원이 소비되는지 계산하면 사용자에게 그 비용을 요구하는데 그게 가스.

함수의 로직( = 논리구조 )가 얼머나 복잡한지에 따라 달라진다.

이는 고의적으로 네트워크를 방해하도록 반복문을 사용해 자원을 낭비하게 하는것을 막기위함이다.

 

솔리디티에서는 uint의 종류에 관계없이 256비트를 잡아놓아서 작은 단위를 쓴다고 가스소모를 덜 하지는 않는다.

하지만 구조체는 예외로 친다.

  1. uint 크기가 작을 수록 가스 소모를 덜 한다.
  2. 비슷한 크기의 uint끼리 묶어놓으면 가스 소모를 덜한다. ( Hello보다 MiniMe가 소모를 덜 한다.)
struct NormalStruct {
  uint a;
  uint b;
  uint c;
}

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

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

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

 

 

 

시간 단위 (Time units)

now : 1970년 1월 1일부터 지금까지의 초 단위 합을 받을 수 있다.

seconds, minutes, hours, days, weeks, years도 가능하다.

uint lastUpdated;

function updateTimestamp() public {
  lastUpdated = now;
}

function fiveMinutesHavePassed() public view returns (bool) {
  return (now >= (lastUpdated + 5 minutes));
}

 

 

 

구조체를 인수로 전달하기

private나 internal 함수 인수로 구조체의 storage 포인터를 전달할 수 있다.

다음은 Zomebie storage 포인터 _zombie를 인수로 받는 internal 함수 _doStuff다.

function _doStuff(Zombie storage _zombie) internal {
  // _zombie로 할 수 있는 것들을 처리
}

 

 

 

인수를 가지는 함수 제어자

driverCar 함수를 호출했을때 먼저 실행되는 함수 제어자 olderThan.

16과 _userId를 인자로 가지고 olderThan 제어자가 실행된다.

 

// 사용자의 나이를 저장하기 위한 매핑
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) {
  // 필요한 함수 내용들
}

 

 

 

View 함수를 사용해 가스 절약하기

블록체인 상에서 아무것도 수정하지 않고 읽기만 하는 view 함수는 트랜잭션을 만들지 않는다.

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

view 함수는 데이터를 읽기 위해 로컬 이더리움 노드에 요청만 날린다.

그러므로 DApp의 가스 사용을 최적화하기 위해 읽기 기능을 사용하는 곳에 external view를 사용하는 것이 좋다.

( 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수 없는걸 빼면  public과 같은 external. )

 

 

 

메모리에 배열 선언하기

stoarage를 쓰는 연산은 솔리디티에서 비싼 연산 중 하나다.

stoarage에 쓰는건 블록체인에 영구적으로 기록되기 때문이다.

= 사용자의 모든 블록에 기록된다.

= 사용하는 데이터 양이 늘어난다.

 

배열에 memory 키워드를 사용하면 storage에 아무것도 쓰지 않아도 함수 안에 새로운 배열을 만들 수 있다.

storage의 배열을 직접 업데이트 하지 않기 때문에 가스를 덜 소모한다.

비동기 배열처럼 push로 크기가 조절되지 않기때문에 메모리 배열은 반드시 길이 인수와 함께 생성되어야한다.

function getArray() external pure returns(uint[]) {
  // 메모리에 길이 3의 새로운 배열을 생성한다.
  uint[] memory values = new uint[](3);

  values.push(1);
  values.push(2);
  values.push(3);
  return values;
}

 

 

 

for 반복문 사용하기

사용하는 이유

다른 사용자에게 내가 갖고있는 nft를 판매 =  사실상 nft 배열 중에서 하나가 빠져나가는것.

이 배열을 재정렬하기 위해서 배열의 요소들을 한칸씩 이동하는건 너무나 많은 가스비용이 필요하다.

심지어 storage에 작성하는 동작이기때문에 더 많이 필요하다!

그래서 가스를 사용하지 않는 view를 사용해서 필요한 nft에만 접근해서 새로운 배열을 만들 수 있을것이다.

function getEvens() pure external returns(uint[]) {
  uint[] memory evens = new uint[](5);
  uint counter = 0;    // 새로운 배열의 인덱스를 추적하는 변수
  
  for (uint i = 1; i <= 10; i++) {
    if (i % 2 == 0) {          // `i`가 짝수라면...
      evens[counter] = i;      // 배열에 i를 추가함
      counter++;               // `evens`의 다음 빈 인덱스 값으로 counter를 증가시킴
    }
  }
  return evens;
}

 

댓글