Crypto Zombie로 Solidity 처음부터 공부하기 [Lesson 3]

Sungmin Oh·2021년 3월 16일
0

솔리디티의 특성

불변성

내가 솔리디티 코드를 짜서 이더리움에 컨트랙트를 배포하고 나면, 컨트랙트는 변하지 않는다. 즉, 컨트랙트를 수정하거나 업데이트 할 수 없다는 의미이다. 이는 콘트랙트에 치명적인 결함이 있을 때 수정할 수 없기에 단점으로 작용하기도 하지만, 그 누구도 배포 이후에 컨트랙트를 무단으로 바꿀 수 없다는 점에서 신뢰성을 보장해주기도 한다.

외부 의존성

컨트랙트는 코드에 다른 컨트랙트의 주소를 포함하는 방식으로 외부 컨트랙트와 상호작용한다. (Lesson 2의 인터페이스 참조) 이 때문에 내 컨트랙트와 상호작용 하는 외부 컨트랙트에 심각한 버그가 생긴다면 나의 컨트랙트까지 문제를 일으킬 수 있다. 솔리디티의 불변성 때문에 내 코드를 수정할 수도 없기 때문에 이런 문제를 회피하기 위해서 내 DApp의 중요한 부분을 수정할 수 있도록 하는 함수를 미리 만들어 놓기도 한다.

소유 가능한(ownable) 컨트랙트

우리의 컨트랙트에는 DApp의 중요한 부분을 수정할 수 있도록 하는 중요한 함수들도 있을 것이다. external이나 public으로 선언되어있는 이러한 함수들은 누구나 사용할 수 있기 때문에 보안에 심각한 문제가 생길 수 있다.
이러한 문제를 해결하기 위해서 사용하는 것이 컨트랙트를 소유 가능하게 만드는 것이다. 컨트랙트를 소유 가능하게 만들기 위해 우리는 솔리디티 라이브러리에서 아래와 같은 ownable 컨트랙트를 가져온다.

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;
  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
  }

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

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}

위의 컨트랙트를 우리의 컨트랙트에서 상속함으로써 우리는 소유 가능한 함수를 만들 수 있다. 위의 컨트랙트에서 아직 배운 적 없는 새로운 요소들이 있다.

생산자(Constructor)

컨트랙트와 동일한 이름을 가진 특별한 함수로, 컨트랙트가 생성될 때 딱 한 번만 실행된다. (위 컨트랙트에서는 function Ownable이 생성자)

함수 제어자(Function Modifier)

일종의 유사 함수. 우리가 지금까지 배운 public, private 등과 같이 함수 뒤에 붙는 제어자를 직접 만들어 쓸 수 있다. 위의 컨트랙트에서는 modifier onlyOnwer()이 함수 제어자이다. 끝에 _; 가 붙는다. 함수 제어자는 아래와 같이 선언한다.

modifier 함수 제어자 이름(자료형 인수명) {
    ...
    _;
}

예를 들어서 아래와 같은 함수 제어자를 선언했다고 해보자.

modifier onlyOwner() {
    require(msg.sender == owner);
    _;
}

그러면 이 제어자를 아래와 같이 사용할 수 있다.

contract MyContract is Ownable {

    function OnlyMe() external onlyOwner {
        string Hello = "Hello";
    }
}

이렇게 코드를 작성하면, OnlyMe 함수를 호출했을 때, 함수의 내용이 실행되기 전에 먼저 onlyOwner의 코드가 실행된다. 여기서 require문의 조건을 통과하면 _;을 통해 OnlyMe 함수로 되돌아와 아래 코드를 실행하게 되는 것이다.
즉, 함수에 onlyOwner를 추가한다면 그 함수는 오직 컨트랙트의 소유자만이 호출할 수 있게 되는 것이다.

가스 (gas)

솔리디티에서는 사용자들이 내가 만든 함수를 실행시킬 때마다 가스라고 하는 화폐를 지불해야 한다. 사용자가 가스를 이더를 이용해서 사기 때문에 나의 DApp 함수를 실행하려면 이더를 소모해야 하는 것이다.

함수를 실행하는 데 얼만큼의 가스가 필요한지는 그 함수의 로직 구조가 얼마나 복잡한지에 따라 결정된다. 각각의 연산은 소모되는 가스 비용이 있고, 그 연산을 사용하는 데에 소모되는 컴퓨팅 자원의 양이 이 비용을 결정한다. 함수 전체의 가스 비용은 그 함수를 구성하는 개별 연산들의 가스 비용을 모두 합친 것과 같다.

함수를 실행하는 것이 사용자들에게 실제 돈을 사용하게 하기 때문에 코드 최적화는 매우 중요하다. 최적화가 잘 안되어있다면 사용자들에게 불필요한 가스 비용을 청구하게 될 수도 있기 때문이다.

이런 가스 비용을 왜 발생하게 되는 것인가? 이더리움에서는 내가 어떤 함수를 실행할 때 네트워크 상의 모든 개별 노드가 함수의 출력값을 검증하기 위해 그 함수를 실행한다. 이러한 구조가 이더리움을 분산화하고 데이터를 위조 가능성 없게 안전하게 보관하는 데 기여한다. 그러나 이러한 구조로 인해 누군가 무한 반복문을 써서 네트워크를 방해하거나 자원 소모가 큰 연산을 써서 네트워크 자원을 모두 사용할 수 없게 만들 수 있게 되었다. 그래서 연산 처리에 비용이 들어가도록 만들었고, 사용자들은 저장 공간과 연산 사용 시간에 따라서 비용을 지불해야 하게 되었다.

시간 단위

솔리디티에서는 시간을 다룰 수 있는 단위계를 기본적으로 제공한다. 솔리디티에서 시간의 기본 단위는 '초(second)'이다. Lesson 2에서 배운 msg.sender와 비슷하게 언제나 사용할 수 있는 전역 변수로 now가 있다. now 변수를 사용하면 현재의 유닉스 타임스탬프 값을 얻을 수 있다. 유닉스 타임스탬프 값은 1970년 1월 1일부터 지금까지 몇 초가 흘렀는지에 대한 값이다.

또한 솔리디티에서는 seconds, minutes, hours, days, weeks, years 같은 시간 단위를 지원한다. 이 시간 단위들은 그 시간에 해당하는 초 단위 숫자로 변환된다. 예를 들어 1 minutes는 60으로, 1 hours는 3600으로 변환된다.

가스 절약하기

  • view 함수 이용하기
  • memory에 배열 선언하기

(이후에 더 깊게 공부해서 해당 내용 보충할 예정)

for 반복문

for (uint i = 1 ; i <= 10 ; i++) {
    uint sum = 0;
    sum = sum + i;
}

이 내용은 정보를 공유하기 위함이 아닌 저의 개인적인 공부 기록을 남기기 위함입니다. 따라서 틀린 정보가 있을 수 있으니 유의해서 봐주시면 감사하겠습니다.

profile
ambitious person

0개의 댓글