3주차 공부

박세연·2021년 1월 14일
1

Mastering Ethereum

목록 보기
3/10
post-thumbnail

Chapter 7. 스마트 컨트랙트와 솔리디티

스마트 컨트랙트란?

스마트 컨트랙트 정의 : 당사자들이 다른 약속에 따라 수행하는 프로토콜을 포함하여 디지털 형식으로 지정된 일련의 약속. 불변적인 컴퓨터 프로그램

1. 컴퓨터 프로그램
2. 불변성
3. 결정론적
4. EVM 컨텍스트
5. 탈중앙화된 월드 컴퓨터

컨트랙트 ABI

리믹스 IDE 사용

해당 코드를 컴파일하면, ABI파일을 얻을 수 있다.

// Contract JSON ABI
[
	{
		"constant": false,
		"inputs": [
			{
				"name": "withdraw_amount",
				"type": "uint256"
			}
		],
		"name": "withdraw",
		"outputs": [],
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"payable": true,
		"stateMutability": "payable",
		"type": "fallback"
	}
]
  • ABI(Application Binary Interface): 두 프로그램 모듈 간 또는 때때로 운영체제와 사용자 프로그램 간의 인터페이스.
    데이터 구조와 함수가 기계 코드에서 사용되는 방법을 정의한다.
  • 이더리움에서 ABI는 EVM에서 컨트랙트 호출을 인코딩하고 트랜잭션에서 데이터를 읽는데 사용
  • ABI의 목적은 컨트랙트에서 호출할 수 있는 함수를 정의하고 각 함수가 인수를 받아들이고 결과를 반환하는 방법을 설명해주는 것

솔리디티 프로그래밍

컨트랙트 정의

솔리디티의 주요 데이터 타입은 contract이다.
객체 지향 언어의 객체와 마찬가지로 컨트랙트는 데이터와 메서드가 포함된 컨테이너


솔리디티는 컨트랙트와 유사한 두 가지 객체 유형을 제공한다.
1. interface : 컨트랙트의 형태를 지정
2. library : 라이브러리 컨트랙트는 delegatecall 메서드를 사용하여 한 번만 배포되고 다른 컨트랙트에서 사용되기 위한 컨트랙트

데이터타입

  • 부울(bool) : true, false
  • 정수(int, uint) : 디폴트는 256비트. int8 ~ int256까지 8비트씩 증가하는 정수
  • 고정소수점(fixed, ufixed) : ex. fixexM×N - M비트 단위 크기, N은 소수점 이하 자릿수
  • 주소 : 20바이트 이더리움 주소
  • 바이트 배열(고정 크기) : 고정크기 바이트 배열 (bytes1 ~ bytes32)
  • 바이트 배열(가변 크기) : bytes 또는 string으로 선언된 가변 크기 바이트 배열
  • 열거형 : enum
  • 배열 : 모든 유형의 고정 또는 동적 배열
  • 구조체 : 사용자 정의 데이터 컨테이너
  • 매핑 : mapping(KEY_TYPE => VALUE_TYPE)

  • 시간 단위(time units) : seconds(기본), minutes, hours, days
  • 이더 단위(ether units) : wei(기본), szabo, ether

사전 정의된 글로벌 변수 및 함수

트랜잭션/메시지 콜 컨텍스트

msg 객체 : 해당 컨트랙트 실행을 시작한 트랜잭션 호출 또는 메시지 호출

  • msg.sender : 컨트랙트를 호출한 주소(EOA 혹은 컨트랙트)
  • msg.value : 호출 시 전송된 이더의 값 (단위는 wei)
  • msg.gas(솔리디티 v0.4.21 에서는 gasleft 함수로 대체) : 이 실행 환경의 가스 공급에 남은 가스의 양
  • msg.data : 이 호출의 데이터 페이로드가 컨트랙트에 포함
  • msg.sig : 함수 선택자인 데이터 페이로드의 처음 4바이트

트랜잭션 컨텍스트

tx 객체 : 트랜잭션 관련 정보에 접근하는 방법을 제공

  • tx.gasprice : 트랜잭션을 호출하는데 필요한 가스 가격
  • tx.origin : 이 트랜잭션에 대한 원래 EOA의 주소 (안전하지 않음)

블록 컨텍스트

block 객체 : 현재 블록에 대한 정보가 포함된다.

  • block.blockhash(blockNumber) : 솔리디티 v0.4.22의 blockhash함수로 대체. 지정된 블록 번호의 블록 해시
  • block.coinbase : 현재 블록 수수료 및 보상의 수취인 주소
  • block.difficulty : 현재 블록의 난이도(PoW)
  • block.gaslimit : 현재 블록에 포함된 모든 트랜잭션에 소요될 수 있는 최대 가스 양
  • block.number : 현재 블록 번호
  • block.timestamp : 채굴자가 현재 블록에 넣은 타임스탬프

address 객체

  • address.balance : 해당 주소의 잔액(wei) ex. 현재 컨트랙트의 잔액 = address(this).address
  • address.transfer(amount) : 해당 주소로 금액(wei 단위)을 전송. 오류가 발생할 경우 예외처리
  • address.send(amount) : 금액을 보내는데, 오류가 발생하면 false를 반환한다.
  • address.call(payload) : 컨트랙트에서 또 다른 컨트랙트의 함수를 호출. 오류가 발생하면 false 반환

내장 함수

  • addmod. mulmod : addmod(x, y, k) = ( x + y ) % k, mulmod(x, y, k) = (x * y ) % k
  • keccak256, sha256, sha3, ripemd160 : 해시 함수
  • ecrecover : 서명에서 메시지 서명에 사용된 주소를 복구
  • selfdestrunct(recipient_address) : 현재 컨트랙트를 삭제하고 계정의 나머지 이더를 받는 사람의 주소로 보낸다.
  • this : 현재 실행중인 컨트랙트 계정의 주소

함수

컨트랙트 내에서 EOA 트랜잭션이나 또 다른 컨트랙트에 의해 호출될 수 있는 함수를 정의한다.

function FunctionName([parameters]) {public|private|internal|external}[pure|constant|view|payable] [modifier] [returns (return types)]

  • FunctionName : 함수를 호출하는데 사용되는 함수의 이름.
    각 컨트랙트마다 한 개의 함수가 이름 없이 정의될 수 있는데 이것을 폴백(fallback)함수라고 하고, 다른 함수 이름이 없을 때 호출

  • parameters : 함수에 전달되어야 하는 인수를 지정

  • public(디폴트) : 다른 컨트랙트, EOA 트랜잭션, 또는 해당 컨트랙트 내에서 호출 가능
  • private : 해당 컨트랙트 내의 다른 함수들만 호출 가능한 함수
  • external : 키워드 this가 앞에 붙지 않으면 컨트랙트 내에서 호출할 수 없음
  • internal : 컨트랙트 내의 다른 함수들만 호출 가능. 단, 해당 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다.

  • view : 함수가 데이터를 보기만 하고 상태를 변경하지 않는다. (constant는 이 전 버전에서 view의 별칭)
  • pure : 스토리지에서 변수를 읽거나 쓰지 않는 함수. 어떤 데이터에도 접근하지 않고 데이터의 반환만 가능
  • payable : 입금을 받을 수 있는 함수

컨트랙트 생성자 및 selfdestruct

생성자 함수: 컨트랙트가 생성될 때 오직 한번만 사용되는 특별한 함수
생성자는 컨트랙트 생성과 동일한 트랜잭션에서 실행된다.


✔️ 솔리디티 v0.4.21까지의 생성자 지정 방법

contract MEContract {
  function MEcontract() {
 	// 생성자 
  }
}

🔺 문제점 : 생성자 함수 이름은 변경되지 않은 상태에서 컨트랙트 이름만 변경될 경우, 해당 함수가 더는 생성자 함수로 작동하지 않음

✔️ 솔리디티 v0.4.22 이후

pragma ^0.4.22
contract MEContract {
  constructor() {
    // 생성자
  }
}

🔺 컨트랙트의 이름을 변경해도 생성자에는 영향이 없다.

SELFDESTRUCT(자기 파괴) - 컨트랙트 생명 주기의 끝: 컨트랙트 소멸(contract destruction)

selfdestruct(address recipient); // 컨트랙트를 삭제할 수 있는 유일한 방법

삭제 가능한 컨트랙트 생성을 원한다면 컨트랙트에 명시적으로 위의 코드를 추가하면 된다.
어떤 컨트랙트가 영원히 지속되는 것을 보장하기 위해서는 해당 코드를 포함하지 않으면 된다.

함수 변경자(function modifier)

컨트랙트 내에서 함수에 적용되어야 할 조건을 생성하기 위해 사용된다.

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

✔️ onlyOwner 함수 변경자를 적용하는 모든 함수는 위 내용의 조건을 만족해야 함수를 실행할 수 있다.

function destroy() public onlyOwner {
  selfdestruct(owner);
}

✔️ destroy함수를 호출한 계정이 컨트랙트의 owner 주소와 동일해야 selfdestruct를 실행할 수 있다.
✔️ 즉, owner만이 컨트랙트를 파기할 수 있다.

컨트랙트 상속 (is 키워드)

contract Child is Parent {
	...
}
contract Chile is Parent1, Parent2 {
	...
}

✔️ Child 컨트랙트가 Parent 의 모든 메서드, 기능 및 변수를 상속한다. (사용할 수 있다.)

에러 처리(assert, require, revert)

  • assert : 어떤 조건의 결과가 참일 것으로 예상될 때 사용. 만약 거짓이면 에러로 실행 중지
  • require : 입력값이 설정한 조건의 기댓값에 맞는지를 확인. 만약 맞지 않다면 에러로 실행 중지

이벤트

트랜잭션이 완료되면(실패 혹은 성공) 트랜잭션 영수증(receipt)이 발행되는데, 이 영수증은 트랜잭션의 실행 동안 발생했던 행위에 관한 정보를 제공하는 로그(log) 엔트리를 갖고있다.
로그(log)를 만들기 위해 사용하는 솔리디티의 고수준 객체가 이벤트(event)


✔️ Withdrawal 은 출금을 로깅하는 이벤트
✔️ Deposit 은 입금을 로깅하는 이벤트
✔️ emit : 트랜잭션 로그에 이벤트 데이터를 집어넣기 위한 키워드

다른 컨트랙트 호출 (send, call, callcode, delegatecall)

컨트랙트 내에서 다른 컨트랙트를 호출하는 것은 매우 유용하지만, 위험을 내포한 작업
✔️ 다른 컨트랙트를 호출하기 위한 가장 안전한 방법은 직접 다른 컨트랙트를 만드는 것

📌새로운 인스턴스 만들기

✔️ 다른 컨트랙트를 호출하기 위한 가장 안전한 방법은 직접 다른 컨트랙트를 만드는 것

import "Faucet.sol"; // Faucet 컨트랙트 정의가 다른 파일에 있는 경우
contract Token is mortal {
  Faucet _faucet;
  constructor() {
    _faucet = new Faucet();
    // _faucet = (new Faucet).value(0.5 ether)(); // 인스턴스 생성 시 선택적으로 이더 전송 값을 지정할 수 있다.
  }
  function destroy() ownerOnly {
    _faucet.destroy();
}

📌존재하는 인스턴스에 주소 부여하기

✔️ 다른 컨트랙트를 호출할 수 있는 또 다른 방법은 이미 존재하는 해당 컨트랙트의 인스턴스에 주소를 캐스팅하는 방법

import "Faucet.sol"; // Faucet 컨트랙트 정의가 다른 파일에 있는 경우
contract Token is mortal {
  Faucet _faucet;
  constructor(address _f) {
    _faucet = Faucet(_f); // 생성자 _f에 대한 인수로 제공된 주소를 가져와서 Faucet객체로 형변환
    _faucet.withdraw(0.1 ether);
  }
}

실제로 주소가 Faucet 객체인지의 여부를 확실히 알 수 없기 때문에 더 위험한 방법

가스 고려사항

가스(gas)는 스마트 컨트랙트 프로그래밍에서 매우 중요한 고려사항 (뒤의 챕터에서 더 자세히 설명할 예정)
✔️ 가스는 이더리움에서 트랜잭션이 사용하도록 허용할 최대 계산량을 제한하는 자원.
계산을 하는 동안 가스 한계를 초과하면 아래와 같은 이벤트가 발생한다.

  1. '가스 부족(out of gas)' 예외가 발생한다.
  2. 실행 전의 컨트랙트 상태가 복원된다.(복귀)
  3. 가스를 지급하는 데 사용되는 모든 이더는 트랜잭션 수수료로 간주되고 환불되지 않는다.

📌가스 비용을 최소화하기 위해 권장하는 지침

✔️ 동적 크기 배열 피하기
✔️ 다른 컨트랙트 호출 피하기



Chapter 8. 스마트 컨트랙트와 바이퍼

  • 바이퍼(vyper) : 개발자들이 이해하기 쉬운 코드를 작성할 수 있도록 함으로써 뛰어난 감사 용이성을 제공하고자 만들어진, EVM을 위한 컨트랙트용 프로그래밍 언어
    ☑️ 바이퍼의 주요 원칙: 개발자들이 오독하기 쉬운 코드를 작성할 수 없게 하자.

스마트 컨트랙트의 취약점

1. 자기 파괴 컨트랙트(suicidal contract)
    아무 주소를 이용해서 삭제시킬 수 있는 스마트 컨트랙트
2. 탐욕 컨트랙트(greedy contract)
    이더를 빼올 수 없도록 막아버리는 상태에 도달할 수 있는 스마트 컨트랙트
3. 방탕한 컨트랙트(prodigal contract)
    이더를 아무런 주소로 보낼 수 있게 만든 스마트 컨트랙트

이렇게 잘못된 스마트 컨트랙트 코드는 이더리움 사용자에게 예상치 못한 자금 손실을 초래하므로 바람직하지 않다.
➡️ 바이퍼는 이런 종류의 취약점이 있는 코드를 작성하기 어렵게 하여 안전한 코드를 작성하기 쉽게 해준다.

솔리디티와 비교 - 바이퍼(vyper)

✔️ 바이퍼가 안전하지 않은 코드 작성을 방지하는 방법 중 하나는 의도적으로 솔리디티의 기능 중 일부를 생략(omitting)하는 것이다.

변경자(modifier)

변경자는 함수 호출시 사전 검사를 수행할 뿐 아니라, 변경자로서 호출 함수의 컨텍스트에서 스마트 컨트랙트의 환경에 영향을 줄 수 있다.


개발자가 a라는 public 함수를 작성하는 경우라고 가정.

  • 개발자가 stageTimeConfirmation 변경자가 nextStage()함수를 호출한다는 것을 모를 수도 있다.
  • 단순히 함수a()를 호출하는 것이 스마트 컨트랙트 상의 stage 변수를 바꾸는 결과를 초래할 수 있다.

➡️ 바이퍼는 변경자를 없애버렸다.

  • 변경자를 검증만을 위해 사용한다면, 간단히 인라인(inline)체크를 사용해서 함수의 일부분으로 포함해 검증
  • 스마트 컨트랙트의 상태를 변화시키기 위해 사용했다면, 해당 내용을 명시적으로 함수의 일부분으로 만든다.

클래스 상속

상속을 통해 프로그래머는 기존 소프트웨어 라이브러리에서 기능, 속성 및 동작을 가져와서 미리 작성된 코드를 활용할 수 있다. (코드 재사용에 유용하다.)
➡️ 바이퍼는 상속을 지원하지 않는다.
바이퍼는 다중 상속이 코드를 이해하기 어렵고 복잡하게 만든다는 견해를 갖고 있다.

인라인 어셈블리

인라인 어셈블리는 개발자에게 EVM에 대한낮은 수준의 접근을 제공하여 솔리디티 프로그램이 EVM 명령어에 직접 접근하여 작업을 수행할 수 있게 한다.
➡️ 바이퍼는 인라인 어셈블리 사용이 가독성 손실이라고 여기기때문에 지원하지 않는다.

함수 오버로딩

함수 오버로딩은 여러 함수를 같은 이름으로 작성할 수 있게 해주는 기능

function f(uint _in) public pure returns (uint out) {
  out = 1;
}
function f(uint _in, bytes32 _key) public pure returns (uint out) {
  out = 2;
}

➡️ 바이퍼는 각기 다른 인수를 사용하는 동일한 이름의 여러 함수 정의가 혼란스러울 수 있으므로 함수 오버로딩을 지원하지 않는다.

변수 형변환

  • 암시적 형변환 : 유형 변환이 의미상 안전하고 손실되는 정보가 없으면 컴파일러는 암시적 형변환을 수행할 수 있다.
    ex. uint8 => uint16 (컴파일 타임에 수행)
  • 명시적 형변환 : 데이터의 손실이 발생할 수 있다.
    ex. uint32 => uint16

➡️ 바이퍼에는 명시적 형변환을 수행하는 convert 함수가 있다.

  • 개발자가 conver를 호출하면 conversion_table을 참고하여 적절한 변환을 수행하도록 한다.
  • 이렇게 바이퍼에서는 명시적 형변환을 선택해서 개발자가 모든 형변환을 수행할 책임을 갖도록 한다.

전제 조건과 사후 조건

➡️ 바이퍼는 전제 조건, 사후 조건, 상태 변경을 명시적으로 처리하여 중복 코드가 생성되더라도 최대한의 가독성과 안전성을 보장한다.

바이퍼로 스마트 컨트랙트를 작성할 때 검토할 세 가지 사항

1. 조건(condition)
이더리움의 상태 변수의 현재 상태/조건은 무엇인가?

2. 효과(effect)
이 스마트 컨트랙트 코드가 실행 시 상태 변수의 조건에 어떤 영향을 미칠 것인가?
어떤 영향을 받고, 어떤 영향을 받지 않을 것인가?
이러한 효과들이 스마트 컨트랙트의 의도와 일치하는가?

3. 상호작용(interaction)
앞의 두 고려사항을 철저히 다룬 후, 컨트랙트를 배포하기 전에 코드를 논리적으로 단계별로 실행하고 다른 컨트랙트와의 상호작용을 포함하여 발생할 수 있는 모든 영구적인 결과, 그에 따른 효과, 시나리오를 고려.

장식자

➡️ 바이퍼는 장식자(decorator)를 각 함수의 시작 부분에 사용할 수 있다.

@private

컨트랙트를 외부에서 함수에 접근할 수 없게 한다.

@public

함수를 공개적으로 볼 수 있고 실행할 수 있게 한다.

@constant

@constant 장식자가 있는 함수는 상태 변수를 변경할 수 없다.

@payable

@payable 장식자가 있는 함수만 값을 전송할 수 있다.

함수와 변수 순서

✔️ 각각의 개별 바이퍼 스마트 컨트랙트는 하나의 바이퍼 파일로 구성된다.
✔️ 즉, 모든 함수, 변수 등을 포함하여 모든 바이퍼 스마트 컨트랙트 코드는 한 곳에 존재한다.
➡️ 바이퍼는 각 스마트 컨트랙트의 함수 및 변수 선언을 물리적으로 특정 순서에 맞게 작성해야 한다.

profile
안녕하세요

0개의 댓글