‘이더리움 댑 개발’ 세미나 7. 6장. The Fundraiser Application 1/2

6장. The Fundraiser Application은 분량도 좀 되고 다루어야 할 내용도 많기 때문에 두 번의 세미나로 나누어서 진행합니다.

이번 세미나에서는 Editing the Beneficiary 까지만 다룹니다.

다음 링크에서 본문 소스 코드를 볼 수 있습니다.  https://github.com/RedSquirrelTech/hoscdev/tree/master/chapter-7

Creating the Project

  • fundraiser 디렉토리를 생성하고, 해당 디렉토리로 이동합니다.
  • truffle unbox react 명령을 실행합니다.
    • 트러플은 프로젝트를 쉽게 할 수 있도록 템플릿을 제공하고 가져다 쓸 수 있도록 합니다. 템플릿을 박스라고 하고 unbox해서 가져다 사용할 수 있습니다.
      • truffle init 대신 truffle unbox를 사용해서 프로젝트를 좀 더 쉽게 시작할 수 있습니다.
    • react 박스는 리액트로 UI를 만들 수 있도록 프로젝트를 구성해 두었습니다.
      • client 디렉토리에 관련 파일들이 생성됩니다.
  • 불필요한 파일들을 삭제합니다.
    • contracts/SimpleStorage.sol
    • migrations/2_deploy_contracts.js
    • test 디렉토리의 모든 파일들
  • contracts/Fundraiser.sol 파일과 test/fundraiser_test.js 파일을 추가합니다.

Initializing Fundraisers

그림 6.2는 새로운 기금 모금(fundraiser)을 만들기 위한 화면입니다.

기금 모금 생성을 위한 정보는 다음과 같습니다.

  • 수혜자(beneficiary) 정보
    • name, website, image url, description
  • beneficiary address
    • 모금 된 기금을 받을 계정(주소)
  • custodian 또는 owner address
    • 모금된 기금을 관리하는 계정(주소)

 

수혜자 이름(name)이 생성 시에 설정되는지를 테스트 합니다.

  • fundraiser_test.js에 최 상위 테스트 스위트를 “Fundraiser”로, 테스트 스위트를 “constructor()”로,  테스트 케이스를 “it gets the beneficiary name”로 작성합니다.
      • 컨트랙트는 배포 시에 인스턴스가 생성되기 때문에 매개변수를 갖는 생성자인 경우 배포 시에 생성자에 해당하는 인자들을 넘겨주어야 합니다. 하지만 기금 모금 생성은 컨트랙트 배포 시에 생성자의 인자를 결정할 수 없습니다. 새로운 기금 모금 생성 페이지에서 작성한 기금 모금 정보에 따라 생성자의 인자가 결정되어야 합니다. 
        • 다른 방법이 필요합니다. 다음 장에서는 이러한 경우를 다루기 위해  FundraiserFactory라는 것을 제시합니다. 
        • 이번 장에서는 배포하지 않고 컨트랙트를 테스트할 수 있는 방법을 사용합니다. 
          •   deployed 함수를 사용하지 않고 new 함수를 사용합니다.
            • FundraiserContract.new(name)
  • 테스트가 통과할 수 있도록 Fundraiser 컨트랙트를 작성합니다.

 

나머지 정보들에 대해서도 테스트 케이스를 작성합니다. 다음은 url에 대한 테스트 케이스 입니다.

  • 테스트가 통과되도록 Fundraiser 컨트랙트를 수정합니다.
  • 생성자가 두 개의 매개변수를 가지므로 첫 번째 테스트 케이스도 수정해야 합니다.
  • 매번 테스트 케이스마다 반복해서 작성할 부분이 생깁니다. before() 함수를 사용해서 한 번만 Fundraiser 컨트랙트 인스턴스가 생성되도록 합니다.
    • before()는 첫 번째 테스트 케이스 실행 전에 실행됩니다.
    • after()는 마지막 테스트 케이스 실행 후에 실행됩니다.
    • beforeEach()는 매 테스트 케이스 실행 전에 실행됩니다.
    • afterEach()는 매 테스트 케이스 실행이 마치고 나면 실행됩니다.
  • image url, description, beneficiary, custodian에 대해서도 테스트 케이스를 작성하고, Fundraiser 컨트랙트를 수정합니다.
    • beneficiary와 custodian를 설정하려면 계정 주소가 필요함으로 accounts를 contract() 함수 인자로 받아야 합니다.
      • beneficiary는 accounts[1]로, custodian은 accounts[0]으로 설정합니다.
    • 수혜자 주소로 모금된 이더가 전송되어야 하기 때문에 address payable 타입으로 작성합니다.
    • 관리자 주소는 수혜자 주소를 변경할 수 있는 역할 만 하고 이더를 받지는 않기 때문에 address 타입으로 작성합니다.

Editing the Beneficiary

관리자는 수혜자를 변경할 수 있어야 합니다.

우리는 이미 이전 세미나에서 owner에 대한 개념을 다루었기 때문에 custodian 대신 owner를 적용하도록 합니다.

  • 이전 세미나에서와 같이 오픈제플린을 설치합니다.
    • 최신 버전의 오픈제플린 컨트랙트는 솔리디티 버전이 0.6.0으로 명시되어 있습니다. 설치된 솔리디티 컴파일러가 이 버전 보다 낮은 버전이면 컴파일 오류가 납니다.
      • truffle-config.js 파일을 열고, module.exports 항목으로 networks 다음에 compilers를 추가합니다. 이렇게 하면 해당 솔리디티 컴파일러를 다운로드 합니다.
  • Fundraiser 컨트랙트가 Ownable을 상속받도록 합니다.
    • Ownable.sol을 임포트 합니다.
      • import “openzeppelin-solidity/contracts/access/Ownable.sol”
    • _custodian 상태 변수와 custodian() 함수를 제거합니다.
    • 생성자에서 다음과 같이 custodian을 owner로 설정합니다.
      • transferOwnership(custodian);
  • Fundraiser 컨트랙트 변경 사항에 따라 테스트를 변경합니다.
    • “it gets the custodian” 테스트 케이스에서 fundraiser.custodian()을 fundraiser.owner()로 변경합니다.

 

수혜자를 변경할 수 있도록 테스트 케이스와 Fundraiser 컨트랙트 함수를 추가합니다.

  • “setBeneficiary()”를 테스트 스위트를 작성합니다.
    • 이 테스트 스위트에서도 이전 테스트 스위트에서 작성한 것처럼 컨트랙트 생성을 위한 일을 해야 합니다.
      • contract() 함수에 beforeEach() 함수를 사용해서 매번 테스트 스위트가 실행되기 전에 실행되는 공통 부분을 작성합니다.
      • it updated beneficiary when called by owner account” 테스트 케이스를 작성합니다.
        • 새로운 수혜자를 accounts[2]로 설정합니다.
        • owner만 수혜자를 변경할 수 있기 때문에 owner를 트랜잭션의 from으로 설정합니다.
      • “it throws and error when called from a non-owner account”를 테스트 케이스로 작성합니다. 
        • owner가 아닌 계정으로 setBeneficiary 함수 호출 트랜잭션의 from으로 설정합니다.
  • 테스트가 통과할 수 있도록 Fundraiser 컨트랙트에 setBeneficiary() 함수를 추가합니다.
    • onlyOwner modifier를 사용합니다.

솔리디티 보강

배열

  • 동적 배열 – 새로 생성해서 크기 조정할 때
    • arrs = new int32[](5);
  • 배열을 public 상태변수로 선언하면, 컴파일러는 인덱스를 매개변수로 하는 gettter 함수를 자동생성합니다.

 

매핑

  • 스토리지 데이터 영역에서만 사용할 수 있음
    • 상태 변수 또는 스토리지 참조형으로만 선언할 수 있음
  • 매핑을 public 상태변수로 선언하면, 컴파일러는 키를 매개변수로 하는 gettter 함수를 자동생성합니다.

 

함수

  • 출력 매개변수는 함수가 실행될 때 기본값으로 초기화된 로컬 변수로 생각할 수 있습니다. 모든 출력 매개변수 값이 올바르게 저장된 경우 return문을 사용하지 않아도 됩니다.
  • 다른 컨트랙트의 함수를 호출하면 트랜잭션 메시지가 블록체인에 저장됩니다.
    • 컨트랙트(주소)로 다른 컨트랙트의 인스턴스를 구할 수 있습니다.
      • 예) SampleContract sampleContract = SampleContract(0x4e6c…);
  • this로 public 함수를 호출하면 트랜잭션 메시지가 블록체인에 저장됩니다.

 

상속

  • 상위 컨트랙트의 생성자 호출
    • 다중 상속을 지원하기 때문에 다수의 생성자를 호출할 수 있어야 합니다. 공백 문자로 구분합니다.
  • 재정의 함수에서 상위 컨트랙트의 함수 호출
    • super 키워드 사용
      • 다중 상속의 경우 상위 컨트랙트의 함수들이 순차적으로 호출됩니다.

 

추상 컨트랙트

  • 구현되지 않은 함수가 하나 이상 포함되면 추상 컨트랙트입니다. 추상 컨트랙트는 인스턴스화할 수 없습니다.

인터페이스

 

라이브러리

  • 클래스 라이브러리와 같이 재사용 가능한 공용 컨트랙트라고 생각하면 됩니다.
  • library 키워드를 사용하고, 상태 변수를 가질 수 없고 상속을 지원하지 않습니다. payable로 이더를 받을 수 없습니다.
  • 재사용은 소스코드 레벨에서도 가능하고, 배포된 컨트랙트를 재사용할 수도 있습니다.
  • 소스코드 레벨에서 재사용할 경우 import하고 정적 함수를 호출하는 것처럼 라이브러리이름.함수이름 형태로 사용할 수 있습니다.
  • 배포된 라이브러리를 호출하는 일반적인 방법은 배포된 라이브러리의 시그니처와 같은 로컬 추상 컨트랙트를 정의하는 것입니다.
    • 추상 컨트랙트(라이브러리 주소)의 형태로 라이브러리 인스턴스를 얻을 수 있습니다.
  • 배포된 라이브러리는 호출 컨트랙트의 컨텍스트에서 실행됩니다. 배포된 라이브러리는 소멸시킬 수 없습니다.

기타

  • 변수 값 다시 초기화
    • delete alpha;
  • 다른 컨트랙트 인스턴스 생성
    • new 키워드 사용

솔리디티 소스 코드 레이아웃

솔리디티의 기본적인 레이아웃은 다음과 같습니다.

  1. 솔리디티 컴파일러 버전
    • pragma solidity <버전>
      • 버전은 범위 또는 ‘^’를 사용해서 명시할 수 있음
  2. 컨트랙트나 라이브러리 임포트
    • import <경로>
      • 프로젝트의 contracts 폴더에 작성되는 컨트랙트는 contracts 폴더를 기준으로 절대 경로를 작성한다.
  3. 컨트랙트 정의
    • contract 키워드 사용

컨트랙트 코드의 레이아웃은 다음과 같이 하기를 추천합니다.

  1. 상태 변수(속성)
  2. 함수(메소드)
    • 생성자
    • external, public
    • internal, private
  3. modifier 정의
  4. 이벤트 정의
  5. 열거형
  6. 구조체

 

Modifier

modifier는 함수의 선행조건이나 후행조건을 체크하기 위해 사용합니다.

  • modifier 키워드 사용
    • ‘_;’에는 modifier를 사용하는 함수의 바디 내용이 들어갑니다.
    • ‘_;’가 체크하는 문장  뒤에 나오면 선행조건을, 앞에 나오면 후행조건을 체크하는 것이 됩니다.
  • 함수에서 사용될 때는 리턴이 없을 때는 함수 선언의 제일 끝에, 리턴이 있는 경우는 returns 앞에 작성합니다.

 

데이터타입

  • 값 타입
    • 불린(bool)
      • true, false
    • 정수
      • int8에서 int256까지 8씩 커지는 타입을 가짐, uint8에서 uint256까지도 마찬가지
        • int256은 int로, unit256은 uint로 사용 가능
    • Fixed Point Numbers
      • fixedMxN, ufixedMxN으로 작성
        • M은 비트 수, N은 소수점 자리수
          • M은 8에서 256까지 8씩 커짐, N은 0에서 80사이의 값 사용
      • fixed는 fixed128x18, ufixed는  ufixed128x18를 대신해서 사용할 수 있음
    • Address(address, address payable)
      • 20바이트로 표현되는 이더리움 주소를 나타냄
    • Fixed-size byte arrays
      • bytes1에서 bytes32까지 1씩 커짐
      • byte는 bytes1을 대신해서 사용할 수 있음
    • 열거형(enum)
    • 컨트랙트
      • 주소와 같은 의미로 사용
    • 함수형
  • 참조타입
    • 배열
      • bytes는 동적 바이트 배열
      • string은UTF-8 인코딩된 문자를 지원하는 동적 바이트 배열
    • 구조체(struct)
      • 자신의 타입을 갖는 속성을 가질 수 없음
    • 매핑(mapping)
      • 키-값 매핑(다른 프로그래밍 언어의 dictionary와 유사함)
        • 키 타입은 정수, 불린, 주소, 바이트, 문자열만 가능, 값 타입은 모두 가능
      • 열거하는 것이 불가능, 즉 반복문에서 키값쌍을 열거하면서 처리하는 것이 불가능
About the Author
(주)뉴테크프라임 대표 김현남입니다. 저에 대해 좀 더 알기를 원하시는 분은 아래 링크를 참조하세요. http://www.umlcert.com/kimhn/

Leave a Reply

*