Minwook’s portfolio

3인조직 solidity 스마트 컨트랙트 작성 본문

Codestates 블록체인 부트캠프 6기

3인조직 solidity 스마트 컨트랙트 작성

yiminwook 2022. 10. 8. 17:34

 

구현할 기능

1. 조직의 구성원은 최대 3명.

2. 모든 조직원은 관리자의 권한을 갖는다.

3. 관리자는 외부인을 관리자로 추천할 수 있다.

4. 관리자는 투표를 통하여 구성원을 해임 시킬 수 있다.


수도코드

1. 스마트 컨트랙트 발행자가 첫번째 오너가 된다.

2. 오너는 다른 사람을 추천해서 구성원에 포함시킬 수 있다. 

3. 구성원이 3명 이상일경우 더이상 구성원을 추가 할 수 없다.

4. 구성원이 3명 이상일시 관리자를 해임자를 선출하여 투표를 진행할 수 있다.

5. 모든 구성원이 투표를 완료하면 개표를 진행 할 수 있다.

6. 과반수 이상 찬성하면 해임이 되고, 공석이 된다.

 


필요한 함수

Read(2) 

  1. 투표상황 조회(투표 진행중, 완료)
  2. 각 투표자의 투표상태(투표미완료, 완료)

Write(4)

  1. 후보자추천
  2. 해임자추천
  3. 투표(찬성, 반대)
  4. 개표

코드작성

1. 라이센스와 컴파일할 솔리디티 버전을 작성

추후 ERC-20에 적용시킬 코드이기 때문에 SPDX는 MIT로

컴파일 버전은 0.8.14이상으로 작성하겠습니다 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

 

2. 컨트랙트에 필요한 변수선언

contract OwnerHelper {

  // 관리자가 변경되었을 때 주소와 새로운 관리자의 주소 로그를 남김
  	event OwnershipTransferred(address indexed preOwner, address indexed nextOwner); 
      
  	address private _theOwner; // 최초 관리자

    // 투표의 상태는 투표미완료, 완료 두가지
    enum VoteStatus {
        STATUS_NOT_VOTED, STATUS_VOTED
    }

    // 찬성, 반대 두가지만 던질 수 있다.
    enum Vote {
        disagree, agree
    }

    // 투표 결과 상태
    enum VoteResult {
        STATUS_END, STATUS_PENDING
    }

    struct Voting {
        address Dismsissal; // 해임 대상
        VoteResult voteResult; // 투표 결과 상태
    }

    Voting _voting;
    
    // 관리자 구조체
    struct Owner {
        address addr;  // 관리자 주소
        VoteStatus voteStatus; // 관리자 투표 상태
        Vote vote; // 관리자 투표 
    }

    Owner [] _ownerGroup; //관리자 그룹을 배열로 선언
}

 

3. constructor 작성 

이 계약을 최초 배포한 사람은 최초 관리자가 된다.

constructor() { 
		_theOwner = msg.sender;
        // msg.sender를 _theOwne로 선언
        Owner memory _firstOwner = Owner({
            addr: _theOwner,
            vote: Vote.disagree, // 투표 기본값은 반대
            voteStatus: VoteStatus.STATUS_NOT_VOTED
        });
        _ownerGroup.push(_firstOwner); // 관리자 배열에 넣는다
  	}

 

4. 투표상태, 관리자배열 조회(Read) 함수 작성

   // 투표(_voting) : 후보자 + 투표 결과 상태 리턴 
   function votingStatus() public view virtual returns (Voting memory) {
		return _voting;
  	}

   // 관리자 배열 리턴
   function ownerGroup() public view virtual returns (Owner [] memory) {
		return _ownerGroup;
  	}

 

5. 유효성 검사를 위한 함수 및 modifier 작성

    // 관리자 그룹 안에 있는지 조회하는 함수
    // 관리자이면 true
    function ownerGroupInclude(address _candidate) view private returns (bool){
        for(uint i=0; i<_ownerGroup.length; i++) {
            if(_ownerGroup[i].addr == _candidate) return true;
        } return false;
    }

    modifier ownerGroupNotInCheck(address _candidate) {
        require(!ownerGroupInclude(_candidate),"Already Candidate Includes OwnerGroup");
        _;
    }

    modifier onlyOwner(address _sender) {
        require(ownerGroupInclude(_sender), "Not Includes OwnerGroup! Permission Denied");
        _;
    }
    // 관리자가 세명인지 아닌지 검사하는 함수
    // 세 명일 경우 해임 대상이 있어야 하고 세 명 미만일 경우 해임 대상 불필요
    // 3명 이상일때 false
    function ownerGroupLengthCheck() view private returns (bool){
        if(_ownerGroup.length >=3) return false;
        else return true;
    }

    modifier ownerUnderThree() {
        require(ownerGroupLengthCheck(),"ownerGroup Length Over 3! Please use recommandCandidate Function");
        _;
    }

    modifier ownerOverThree() {
        require(!ownerGroupLengthCheck(),"ownerGroup Length Under 3! Please use recommandDismissal Function");
        _;
    }
    //찬,반 유효성 검사
    modifier isVaildVote(Vote _vote) {
        require((_vote == Vote.agree) || (_vote == Vote.disagree));
        _;
    }
   //현재 투표가 진행 중인지 확인
    modifier votingIsVaild() {
        require(_voting.voteResult == VoteResult.STATUS_PENDING, "The vote is not in progress.");
        _;
    }
   
   //개표전 모든 관리자가 투표를 마쳤는지 확인
    function ownerFinishVotingCheck () private view returns (bool){
        for(uint i=0;i<_ownerGroup.length;i++) {
            if(_ownerGroup[i].voteStatus != VoteStatus.STATUS_VOTED) {
                return false;
            }
        }
        return true; // 다 투표 했을 경우 허가
    }

    modifier ownerFinishVoting () {
        require(ownerFinishVotingCheck(),"Voting is still in progress");
        _;
    }

 

6. 관리자 추천 함수 작성

        //1. 제안자는 관리자 그룹에 존재, 
        //2. 후보자는 ownerGroup에 없어야 하고 
        //3. 관리자 그룹은 세 명미만이여야 함.
        //4. 투표 진행중이 아니어야함
    function recommandCandidate(address _candidate) public 
        onlyOwner(msg.sender) 
        ownerGroupNotInCheck(_candidate) 
        ownerUnderThree() {

        require(_voting.voteResult != VoteResult.STATUS_PENDING,"Already Voting is in progress");

        Owner memory _newOwner = Owner({
            addr: _candidate,
            vote: Vote.disagree,
            voteStatus: VoteStatus.STATUS_NOT_VOTED
        });

        _ownerGroup.push(_newOwner);
    }

 

7. 해임자 추천 함수 작성

        //1. 제안자는 관리자 그룹에 존재
        //2. 해임자는 ownerGroup에 존재(제안자가 스스로 해임가능)
        //3. 관리자 그룹은 세명 이상이어야 함
        //4. 투표중인 상태여야함
    function recommandDismissal(address _target) public 
        onlyOwner(msg.sender) 
        ownerOverThree() {

        //해임자는 ownerGroup에 있어야 함
        require(ownerGroupInclude(_target), "Dismissed Person Not In OwnerGroup");

        // 후보자가 이미 있는 경우 투표가 이미 시작한 경우 필터
        require(_voting.voteResult != VoteResult.STATUS_PENDING,"Already Voting is in progress");

        //투표 시작
        _voting  = Voting({
            Dismsissal : _target,
            voteResult : VoteResult.STATUS_PENDING 
        });
    }

 

8. 투표 함수작성

        //1. 투표자는 관리자 그룹에 존재, 
        //2. 유효한 투표(찬,반)해야 되고(무효표 불가)
        //3. 현재 투표가 진행중이어야 함
    function ownerVoting (Vote _vote) public 
        onlyOwner(msg.sender) 
        isVaildVote(_vote) 
        votingIsVaild() {

        //자신의 투표 상태와 투표를 바꿈
        for(uint i=0;i<_ownerGroup.length;i++) {
            if(msg.sender == _ownerGroup[i].addr) {
                _ownerGroup[i].vote = _vote;
                _ownerGroup[i].voteStatus = VoteStatus.STATUS_VOTED;
                break;
            }
        }
    }

 

9. 개표함수 작성

        //1. 개표 요청 또한 OwnerGroup에 속한 사람만 가능, 
        //2. 투표가 진행중인 상태
        //3. 개표 요청 시 모든 관리자들이 투표를 마친 상태여야 함
    function requestBallotCounting() public 
        onlyOwner(msg.sender) 
        ownerFinishVoting() {

        uint agree = 0; // 찬성
        uint disagree = 0; // 반대

        for(uint i=0;i<_ownerGroup.length;i++) {
            if(_ownerGroup[i].vote == Vote.agree) {
                agree++;
            } else {
                disagree++;
            }
        }

        //과반수 찬성시 해임
        if(agree >= disagree) {
            for(uint i=0; i<_ownerGroup.length; i++) {
                if(_voting.Dismsissal == _ownerGroup[i].addr) {
                    delete _ownerGroup[i];
                    //삭제 후 하나씩 당기기 : delete만 하면 0x00,0,0으로 남음
                    for(uint j=i;j<_ownerGroup.length-1;j++) {
                        _ownerGroup[j] = _ownerGroup[j+1];
                    }
                    //마지막값 pop
                    _ownerGroup.pop();
                    break;
                }   
            }
        } 

        //_voting 투표진행상태 초기화
        _voting.voteResult = VoteResult.STATUS_END;
        _voting.Dismsissal = address(0x00); //해임자

        //owner 투표 상태 및 투표 초기화
        for(uint i=0;i<_ownerGroup.length;i++) {
            _ownerGroup[i].voteStatus = VoteStatus.STATUS_NOT_VOTED;
            _ownerGroup[i].vote = Vote.disagree;
        }
    }

 

 


 

전체코드

/* 
* read (2)
* 1) 투표상황 조회
* 2) 투표자 상태
*
* write (4)
* 1) 후보자추천
* 2) 해임자추천
* 3) 투표
* 4) 개표
*/ 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

contract OwnerHelper {

  // 관리자가 변경되었을 때 주소와 새로운 관리자의 주소 로그를 남김
  	event OwnershipTransferred(address indexed preOwner, address indexed nextOwner); 
      
  	address private _theOwner; // 최초 관리자

    // 투표의 상태
    enum VoteStatus {
        STATUS_NOT_VOTED, STATUS_VOTED
    }

    // 각 관리자가 찬성, 반대 상태
    enum Vote {
        disagree, agree
    }

    // 투표 결과 상태
    enum VoteResult {
        STATUS_END, STATUS_PENDING
    }

    struct Voting {
        address Dismsissal; // 해임 대상
        VoteResult voteResult; // 투표 결과 상태
    }

    Voting _voting;
    
    // 관리자 구조체
    struct Owner {
        address addr;  // 관리자 주소
        VoteStatus voteStatus; // 관리자 투표 상태
        Vote vote; // 관리자 투표 
    }

    Owner [] _ownerGroup; //관리자를 배열로 선언

    // 이 계약을 배포한 사람은 최초 관리자가 됨
    constructor() { 
		_theOwner = msg.sender;
        // 관리자 배열에 넣고 기본값은 반대
        Owner memory _firstOwner = Owner({
            addr: _theOwner,
            vote: Vote.disagree,
            voteStatus: VoteStatus.STATUS_NOT_VOTED
        });
        _ownerGroup.push(_firstOwner);
  	}

    // 투표(_voting) : 후보자 + 투표 결과 상태 리턴 
  	function votingStatus() public view virtual returns (Voting memory) {
		return _voting;
  	}

    // 관리자 배열 리턴
  	function ownerGroup() public view virtual returns (Owner [] memory) {
		return _ownerGroup;
  	}

    // 관리자 그룹 안에 있는지 조회
    // 관리자이면 true
    function ownerGroupInclude(address _candidate) view private returns (bool){
        for(uint i=0; i<_ownerGroup.length; i++) {
            if(_ownerGroup[i].addr == _candidate) return true;
        } return false;
    }

    modifier ownerGroupNotInCheck(address _candidate) {
        require(!ownerGroupInclude(_candidate),"Already Candidate Includes OwnerGroup");
        _;
    }

    modifier onlyOwner(address _sender) {
        require(ownerGroupInclude(_sender), "Not Includes OwnerGroup! Permission Denied");
        _;
    }
    
    // 관리자가 세명인지 아닌지 검사하는 함수
    // 세 명일 경우 해임 대상이 있어야 하고 세 명 미만일 경우 해임 대상 불필요
    // 3명 이상일때 false
    function ownerGroupLengthCheck() view private returns (bool){
        if(_ownerGroup.length >=3) return false;
        else return true;
    }

    modifier ownerUnderThree() {
        require(ownerGroupLengthCheck(),"ownerGroup Length Over 3! Please use recommandCandidate Function");
        _;
    }

    modifier ownerOverThree() {
        require(!ownerGroupLengthCheck(),"ownerGroup Length Under 3! Please use recommandDismissal Function");
        _;
    }

    //찬,반 유효성 검사
    modifier isVaildVote(Vote _vote) {
        require((_vote == Vote.agree) || (_vote == Vote.disagree));
        _;
    }

    //현재 투표가 진행 중인지 확인
    modifier votingIsVaild() {
        require(_voting.voteResult == VoteResult.STATUS_PENDING, "The vote is not in progress.");
        _;
    }

    //개표 요청 전 모든 관리자가 투표를 마쳤는지 확인
    function ownerFinishVotingCheck () private view returns (bool){
        for(uint i=0;i<_ownerGroup.length;i++) {
            if(_ownerGroup[i].voteStatus != VoteStatus.STATUS_VOTED) {
                return false;
            }
        }
        return true; // 다 투표 했을 경우 허가
    }

    modifier ownerFinishVoting () {
        require(ownerFinishVotingCheck(),"Voting is still in progress");
        _;
    }

    //1) 관리자 추천
        //1. 제안자는 관리자 그룹에 존재, 
        //2. 후보자는 ownerGroup에 없어야 하고 
        //3. 관리자 그룹은 세 명미만이여야 함.
        //4. 투표 진행중이 아니어야함
    function recommandCandidate(address _candidate) public 
        onlyOwner(msg.sender) 
        ownerGroupNotInCheck(_candidate) 
        ownerUnderThree() {

        require(_voting.voteResult != VoteResult.STATUS_PENDING,"Already Voting is in progress");

        Owner memory _newOwner = Owner({
            addr: _candidate,
            vote: Vote.disagree,
            voteStatus: VoteStatus.STATUS_NOT_VOTED
        });

        _ownerGroup.push(_newOwner);
    }

    //2) 해임자 투표
        //1. 제안자는 관리자 그룹에 존재
        //2. 해임자는 ownerGroup에 존재(제안자가 스스로 해임가능)
        //3. 관리자 그룹은 세명 이상이어야 함
        //4. 투표중인 상태여야함
    function recommandDismissal(address _target) public 
        onlyOwner(msg.sender) 
        ownerOverThree() {

        //해임자는 ownerGroup에 있어야 함
        require(ownerGroupInclude(_target), "Dismissed Person Not In OwnerGroup");

        // 후보자가 이미 있는 경우 투표가 이미 시작한 경우 필터
        require(_voting.voteResult != VoteResult.STATUS_PENDING,"Already Voting is in progress");

        //투표 시작
        _voting  = Voting({
            Dismsissal : _target,
            voteResult : VoteResult.STATUS_PENDING 
        });
    }

    //3) 관리자 개인 투표 
        //1. 투표자는 관리자 그룹에 존재, 
        //2. 유효한 투표(찬,반)해야 되고(무효표 불가)
        //3. 현재 투표가 진행중이어야 함
    function ownerVoting (Vote _vote) public 
        onlyOwner(msg.sender) 
        isVaildVote(_vote) 
        votingIsVaild() {

        //자신의 투표 상태와 투표를 바꿈
        for(uint i=0;i<_ownerGroup.length;i++) {
            if(msg.sender == _ownerGroup[i].addr) {
                _ownerGroup[i].vote = _vote;
                _ownerGroup[i].voteStatus = VoteStatus.STATUS_VOTED;
                break;
            }
        }
    }

    //4) 개표
        //1. 개표 요청 또한 OwnerGroup에 속한 사람만 가능, 
        //2. 투표가 진행중인 상태
        //3. 개표 요청 시 모든 관리자들이 투표를 마친 상태여야 함
    function requestBallotCounting() public 
        onlyOwner(msg.sender) 
        ownerFinishVoting() {

        uint agree = 0; // 찬성
        uint disagree = 0; // 반대

        for(uint i=0;i<_ownerGroup.length;i++) {
            if(_ownerGroup[i].vote == Vote.agree) {
                agree++;
            } else {
                disagree++;
            }
        }

        //과반수 찬성시 해임
        if(agree >= disagree) {
            for(uint i=0; i<_ownerGroup.length; i++) {
                if(_voting.Dismsissal == _ownerGroup[i].addr) {
                    delete _ownerGroup[i];
                    //삭제 후 하나씩 당기기 : delete만 하면 0x00,0,0으로 남음
                    for(uint j=i;j<_ownerGroup.length-1;j++) {
                        _ownerGroup[j] = _ownerGroup[j+1];
                    }
                    //마지막값 pop
                    _ownerGroup.pop();
                    break;
                }   
            }
        } 

        //_voting 투표진행상태 초기화
        _voting.voteResult = VoteResult.STATUS_END;
        _voting.Dismsissal = address(0x00); //해임자

        //owner 투표 상태 및 투표 초기화
        for(uint i=0;i<_ownerGroup.length;i++) {
            _ownerGroup[i].voteStatus = VoteStatus.STATUS_NOT_VOTED;
            _ownerGroup[i].vote = Vote.disagree;
        }
    }
}

 


 

위 컨트랙트 작성은 코드스테이츠 BEB 6기 동기생인 김현우님의 도움을 받았습니다.

https://apfl99.github.io/

https://github.com/apfl99

 

apfl99 - Overview

apfl99 has 9 repositories available. Follow their code on GitHub.

github.com

 

 

 

 

Comments