Best Practice
- 블록체인 기능이 개발할 app에 반드시 필요한지 확인한다
- 중개자가 없는 P2P 트랜잭션
- 신뢰 범위를 넘어서는 서로 알지 못하는 peer 간의 작동
- 보편적으로 타임스탬프를 가진 변조 불가능한 장부에서 확인, 검증, 저장이 필요한 경우
- 규칙과 정책에 의해 가이드된 자율적인 오퍼레이션을 가진 경우
- 스마트 컨트랙트가 개발할 애플리케이션에 반드시 필요한지 확인해야 한다.
- 스마트 컨트랙트 코드는 간단하고 일관성 잇고 감사 가능하게 작성해야 한다.
- 솔리디티는 자주 업데이트된다는 점 감안하기! (아직 성숙된 언어가 아니다. 고로 컨트랙트 코드와 컴파일러 버전 맞추는 것 주의!)
신뢰
- 확인 : 문제 영역의 일반적인/전역적인 요구 조건을 다루는 것
- 검증 : application / 데이터에 특정한 조건을 다루는 것
신뢰와 무결성을 제공하는 스마트 컨트랙트 설계
(전자 민주주의를 위한 온라인 투표 애플리케이션을 통해)
- 과정 : use case diagram -> 컨트랙트 diagram -> 스마트 컨트랙트 작성
유한 상태 머신 (Finite State Machine) 모델
💡Actor
- 의장 : 투표자 등록, 자신도 스스로 등록하고 투표할 수 있음
- 투표자 : 투표하기
- 불특정 다수 : 투표 과정의 승자나 결과를 요청할 수 있음
💡점진적 개발 단계
1. BallotV1 : 스마트 컨트랙트의 데이터 구조를 정의, 테스트
2. BallotV2 : constructor와 투표 상태를 변화시키기 위한 함수 추가
3. BallotV3 : 스마트 컨트랙트의 다른 함수와 신뢰 구축을 위한 solidity 기능을 보여주기 위한 수정자 추가
4. BallotV4 : 신뢰 요소인 require(), revert(), assert()와 함수 접근 수정자 추가
UML FSM 다이어그램
시간과 여러 조건에 의해 변화하는 스마트 컨트랙트의 상태 변화를 나타내는 다이어그램 (시스템 역동성 표현)
- 후보자 등록은 투표 전 특정 종료 시간까지 마쳐야 함
- 투표 과정은 특정 순서에 따라 실행돼야 함
- 투표는 정해진 기간 동안만 진행
- 투표가 종료돼야만 승자 판단 가능
💡FSM 구성요소
- states : 시작 상태, 종료 상태 (이중 원)
- transitions : 한 상태에서 다른 상태로 변화
- inputs : 상태 변화를 일으키는 입력값 (해당 프로젝트에서는 상태 변화=시간 기반!)
- outputs : 상태 변화 동안 출력되는 것 (0개 이상)
신뢰 중개
Bapp에서는 규칙으로 표현되는 신뢰를 위반하는 트랜잭션을 되돌리거나 중단시켜서 허가받지 않은 트랜잭션이 블록체인의 변조 불가능 장부에 포함되는 것을 방지해야 한다.
solidity가 신뢰 요구 조건을 다룰 수 있는 여러 언어적 기능과 함수 제공
- modifier : 확인해야 할 접근 통제 규칙 명시, 누가 데이터&함수에 통제권을 갖는지 관리 (가시성 수정자와 구분하여 "액세스 수정자"라고 부름)
- require(condition) 선언 : 파라미터로 전달된 조건 검증. 실패할 경우 함수 중단.
- revert() 선언 : 트랜잭션 중단. 블록체인에 기록되는 것을 막아줌 (보통 수정자 정의에 사용됨)
- assert(condition) 선언 : 변수의 조건이나 함수의 실행 과정에서 데이터 검증하고 실패할 경우 트랜잭션 되돌림. 어떤 조건을 만족시키지 못해서 예외 상태가 일어나지 않도록 하기 위해 사용.
💡수정자
modifier name_of_modifier(parameters) {
require { conditions_to_be_checked };
_;
}
modifier validPhase(Phase reqPhase) {
require(state == reqPhase);
_;
}
함수 정의와 수정자 정의를 분리하는 이유 : 신뢰와 무결성 구축을 위해 컨트랙트가 강제하는 규칙을 명확히 보여줄 수 있도록 확인, 검증, 예외 부분 분리
수정자 적용
function register(address voter) public validPhase(Phase.Regs) {
if (msg.sender != chairperson || voters[voter].voted) return;
voters[voter].weight = 1;
voters[voter].voted = false;
}
- 함수의 헤더만 봐도 함수가 다른 것을 처리하기 전에 투표 단계를 체크함을 알 수 있다
코드넣기!!!!!!!!!!!!!!!!!!!!!!!!
- memory 변수 : 블록체인 저장 공간 낭비를 막는다
-> 함수 내에서 로컬 변수로 구조체를 정의할 때, memory / storage 타입을 명시해준다.
테스트 프로세스
- 긍정 테스트 : 유효한 입력값 -> 기대한 바대로 올바르게 작동
- 부정 테스트 : 유효하지 않은 입력값 -> 스마트 컨트랙트가 확인과 검증을 통해 오류를 잡아내고 함수 중단
긍정 테스트
- 의장 account 설정 & 세명의 투표자 register (의장은 배포시 생성자를 통해 등록됨)
- 의장 account 설정 -> 2 인자 입력, stateChange 실행 -> state public 변수 접근 함수를 통해 단계 확인!
- 의장 account 설정 -> 2 인자 입력, vote 실행
- 각 투표자 account 설정 -> 1 인자 입력, vote 실행
- 의장 account 설정 -> 3 인자 입력, stateChange 실행
- reqWinner 실행하면 1후보가 이겼음을 알 수 있음
부정 테스트
- 의장이 아닌 account가 투표자 등록
- 3 state일 때 vote 실행
- 존재하지 않는 후보 번호에 투표
수정자, require(), revert() 사용하기
함수를 실행하는 데에 다수의 규칙이 필요할 경우
modifier onlyChair () {
require(msg.sender == chairperson);
_;
}
function register(address voter) public validPhase(Phase.Regs) onlyChair {
...
}
- 함수 헤드에 빈칸으로 분리된 리스트로 다수의 수정자 적용 가능
-> 두 수정자는 순서대로 실행되는 점 유의
assert() 선언
이 또한 솔리디티의 내장 함수!
어떤 함수 내에서의 연산 과정에서 특정한 조건을 충족했는지 여부를 확인해줌
- 이기기 위해 과반수(ex-3)의 표가 필요하다는 설정
-> reqWinner() 함수에 assert() 문을 추가해서 규칙 추가
- 스마트 컨트랙트에 입력되는 파라미터가 아닌, 함수 내에서 연산이 일어나는 중에 검증한다는 차이점!
ex) assert(winningVoteCount >= 3) 를 통해 최대 투표수가 3 미만이거나 전체 투표자 수가 3미만일 때 함수 중단시킴
- if 구문 대신 require()를 사용하여 조건 실패 시 트랜잭션이 중단된다는 것을 확실히 알 수 있음 -> 어떠한 트랜잭션도 블록체인에 기록되지 않음 by revert()
BallotV4.sol
BallotV3.sol
BallotV2.sol
BallotV1.sol