[멋쟁이 사자처럼 블록체인 스쿨 3기] 23-06-02

임형석·2023년 6월 4일
0

Solidity


Test

오늘은 프로젝트에 맞는 퀴즈를 다시 복습해보았다.

프로젝트의 디파이 보험 플랫폼에는, 보험기금을 굴릴 투표가 필요하다.

이러한 투표 시스템을 만들기 위해 강사님이 내주신 투표 관련 테스트 문제를 복습했다.


안건을 올리고 이에 대한 찬성과 반대를 할 수 있는 기능을 구현하세요.
안건은 번호, 제목, 내용, 제안자(address) 그리고 찬성자 수와 반대자 수로 이루어져 있습니다.(구조체)
안건들을 모아놓은 자료구조도 구현하세요.

사용자는 자신의 이름과 주소, 자신이 만든 안건 그리고 자신이 투표한 안건과 어떻게 투표했는지(찬/반)에 대한 
정보[string => bool]로 이루어져 있습니다.(구조체)

1. 사용자 등록 기능 - 사용자를 등록하는 기능
2. 투표하는 기능 - 특정 안건에 대하여 투표하는 기능, 안건은 제목으로 검색, 이미 투표한 건에 대해서는 재투표 불가능
3. 안건 제안 기능 - 자신이 원하는 안건을 제안하는 기능
4. 제안한 안건 확인 기능 - 자신이 제안한 안건에 대한 현재 진행 상황 확인기능 - (번호, 제목, 내용, 찬반 반환 
	밑의 심화 문제 풀었다면 상태도 반환)
5. 전체 안건 확인 기능 - 제목으로 안건을 검색하면 번호, 제목, 내용, 제안자, 찬반 수 모두를 반환하는 기능
6. 안건 종료 - 투표 진행중, 통과, 기각 상태를 구별하여 알려주고 전체의 70% 
	그리고 투표자의 66% 이상이 찬성해야 통과로 변경, 둘 중 하나라도 만족못하면 기각

1번

1. 사용자 등록 기능 - 사용자를 등록하는 기능

문제를 하나씩 풀어가며 구조체와 맵핑을 선언해보려고 한다.

먼저, 투표에 따른 기본적인 조건을 하나씩 생각해보자.

  1. 유저는 하나의 안건에 한번만 투표할 수 있도록 구현해야 함.
  2. 안건이 통과인지 기각인지 알기위해선, 전체 유저의 수도 알아야 함.
  3. 자신이 진행한 안건을 자신이 종료할 수 있어야 함.

그렇다면, 이렇게 컨트랙트를 작성할 수 있겠다.

    struct user { // 유저의 상태
        string userName;
        address userAddress;
        string[] userProposal;
        mapping(string => voteStatus) userVoteStatus;
    }

    enum voteStatus{ // 유저의 투표 상태
        notvoted,
        voted,
        xvoted
    }
    
    mapping(address => user) users; // 주소값으로 유저를 확인
    
    uint userCount; // 등록된 유저의 수 카운팅
    
    function addUser(string calldata _name) public{
        (users[msg.sender].userName, users[msg.sender].userAddress) = (_name, msg.sender);
        userCount++; // 유저 수 측정
    }

2번

투표하는 기능 - 특정 안건에 대하여 투표하는 기능, 안건은 제목으로 검색, 이미 투표한 건에 대해서는 재투표 불가능

다음은 투표하는 기능이다. 안건을 제목으로 검색 => 투표결과를 변경시켜야 함 => 이미 투표한 건은 재투표 불가.

  1. 안건의 이름, 내용, 주소, 찬성표, 반대표, 안건의 상태를 구조체로 선언.
  2. 안건의 상태를 열거형으로 선언.

require 로 이미 투표를 했는지 확인한 후, _vote 의 값으로 안건의 상태를 수정하면 되겠다.

    struct proposal{
        uint number;
        string subject;
        string detail;
        address by;
        uint accept;
        uint deny;
        proStatus status;
    }
    
        enum proStatus {
        ongoing,
        accepted,
        rejected
    }

    function goVote(string calldata _nameOfProposal, bool _vote) public {
        require(users[msg.sender].userVoteStatus[_nameOfProposal] == voteStatus.notvoted); // 투표를 이미 했다면, 투표가 불가능.
        if(_vote == true){
            proposalM[_nameOfProposal].accept++;
            users[msg.sender].userVoteStatus[_nameOfProposal] = voteStatus.voted;
        } else {
            proposalM[_nameOfProposal].deny++;
            users[msg.sender].userVoteStatus[_nameOfProposal] = voteStatus.voted;
        }
    }

3번

// - 안건 제안 기능 - 자신이 원하는 안건을 제안하는 기능

안건의 제안은 등록된 유저만 허용.

require 를 사용한다.

    function setProposal(string calldata _nameOfProposal, string calldata _detail) public {
        require(isUser[msg.sender] == true); // 등록된 유저만 제안 가능.
        proposalM[_nameOfProposal] = proposal(++proposalCount, _nameOfProposal, _detail, msg.sender,0,0, proStatus.ongoing);
    }

4번

- 제안한 안건 확인 기능 - 자신이 제안한 안건에 대한 현재 진행 상황 확인기능 - (번호, 제목, 내용, 찬반 반환)

내가 제안한 안건을 확인하는 것.

안건에 대한 결과는 누구나 확인할 수 있도록 해야겠지만, 진행중인 안건이라면 제안한 사람만 확인이 가능하도록.

require 를 사용해 제안한 사람이 아니라면, 접근할 수 없도록 한다.

    function checkMyProposal(string calldata _nameOfProposal) public view returns(proposal memory){
        require(proposalM[_nameOfProposal].by == msg.sender,"You are not propose it.");
        return proposalM[_nameOfProposal];
    }

5번

- 전체 안건 확인 기능 - 제목으로 안건을 검색하면 번호, 제목, 내용, 제안자, 찬반 수 모두를 반환하는 기능

전체 안건은 위의 4번문제와 겹친다. 4번의 경우는 내가 제안한 안건을 나만 볼 수 있을때.

지금 문제는 누구나 볼 수 있게 만드는 것. 4번 문제의 require 만 제외하면 같다.

   function getAllProposal(string calldata _nameOfProposal) public view returns(proposal memory){
       return proposalM[_nameOfProposal]; // 안건의 제목만 입력한다면, 누구나 확인가능.
   }

6번

- 안건 종료 - 투표 진행중, 통과, 기각 상태를 구별하고 전체의 70% 그리고 투표자의 66% 이상이 찬성해야 통과로 변경, 둘 중 하나라도 만족못하면 기각

먼저, 진행되고 있는 안건을 종료하는 사람은 항상 안건을 제안한 사람이다.

이것을 require 로 설정해준다. 그리고, if문 안에 수식을 넣어 전체 유저 수와 투표자 수를 비교해서 안건의 통과 여부를 설정한다.

    function completeProposal(string calldata _nameOfProposal) public {
        require(msg.sender == proposalM[_nameOfProposal].by,"You are not propose it");
        if(proposalM[_nameOfProposal].accept + proposalM[_nameOfProposal].deny >= userCount * 7/10 && proposalM[_nameOfProposal].accept >= (proposalM[_nameOfProposal].accept + proposalM[_nameOfProposal].deny) * 66/100 ){
            proposalM[_nameOfProposal].status = proStatus.accepted;
        } else{
            proposalM[_nameOfProposal].status = proStatus.rejected;
        }
    }

테스트

테스트는 편의를 위해, require 를 몇개 지우고 테스트 했다.

하나의 주소로 두개 이상의 유저를 생성할 수 없기 때문이다..

a 라는 안건을 제안하고, 지갑 주소를 바꿔가며 8:3 의 찬반비율을 만들었다.

찬성 8, 반대 3, 안건은 ongoing 상태이다.

여기서 completeProposal 함수로 a 안건을 종료하면 안건의 상태가 accepted 로 바뀐다.

그리고 b 안건을 제안하고, 찬성만 6 표를 했다. 12명의 유저 중에 6 표가 나왔다면, 안건의 상태는 rejected 가 되어야 한다.

completeProposal 함수로 b 안건을 종료했고, 안건의 상태가 rejected 로 바뀌어있다.


테스트 도중, 다음과 같은 문제점을 발견해 곧바로 해결했다.

  1. 같은 이름의 안건을 제안하여 이전의 안건을 덮어 씌워버림.
    안건 제안 기능에 아래의 require 를 한줄 작성하여 해결함.
    require(keccak256(abi.encodePacked(proposalM[_nameOfProposal].subject)) != keccak256(abi.encodePacked(_nameOfProposal)),"This proposal subject already exist");
  1. 유저를 생성하지 않고도 투표, 안건 제안이 가능한 문제. (지갑 주소만 바꿔도 투표가 가능)
    투표와 안건 제안 기능에 아래의 require 를 한줄 작성하여 해결.
    require(isUser[msg.sender] == true,"sign in first.");
  1. 같은 require 을 여러번 사용.
    투표와 안건 제안 기능은 같은 require 를 사용하므로, modifier 를 선언하고 이를 사용함.
modifier requireIsUser(){
        require(isUser[msg.sender] == true,"sign in first.");
        _;
    }

처음 이 문제를 봤을때는 너무 막막했다. 들어가야 할 요소가 너무 많아 무엇을 먼저 해야할지 감이 잘 안잡혔다.

하지만, 구조체와 맵핑만 잘 선언해놓으니 기능을 구현하는 부분은 생각보다 쉬웠다.

프로젝트를 진행하기 전, 확실하게 아키텍쳐를 구성해놓고 시작하는게 중요하단걸 알았다..

항상 코딩하기 전에는 생각부터 하기..


완성된 코드

contract q6 {
    struct proposal{
        uint number;
        string subject;
        string detail;
        address by;
        uint accept;
        uint deny;
        proStatus status;
    }

    struct user {
        string userName;
        address userAddress;
        string[] userProposal;
        mapping(string => voteStatus) userVoteStatus;
    }

    enum voteStatus{
        notvoted,
        voted,
        xvoted
    }

    enum proStatus {
        ongoing,
        accepted,
        rejected
    }

    mapping(string => proposal) proposalM;

    mapping(address => user) users;

    mapping(address => bool) isUser;  // 등록된 유저인지 확인

    uint public userCount;
    uint public proposalCount;

    modifier requireIsUser(){
        require(isUser[msg.sender] == true,"sign in first.");
        _;
    }

    // - 사용자 등록 기능 - 사용자를 등록하는 기능
    function addUser(string calldata _name) public {
        require(isUser[msg.sender] == false,"This address has user account.");
        (users[msg.sender].userName, users[msg.sender].userAddress) = (_name, msg.sender);
        isUser[msg.sender] = true;
        userCount++; // 유저 수 측정
    }

    // - 투표하는 기능 - 특정 안건에 대하여 투표하는 기능, 안건은 제목으로 검색, 이미 투표한 건에 대해서는 재투표 불가능
    function goVote(string calldata _nameOfProposal, bool _vote) public requireIsUser{
        require(users[msg.sender].userVoteStatus[_nameOfProposal] == voteStatus.notvoted); // 투표를 이미 했다면, 투표가 불가능.
        if(_vote == true){
            proposalM[_nameOfProposal].accept++;
            users[msg.sender].userVoteStatus[_nameOfProposal] = voteStatus.voted;
        } else {
            proposalM[_nameOfProposal].deny++;
            users[msg.sender].userVoteStatus[_nameOfProposal] = voteStatus.voted;
        }
    }

    // - 안건 제안 기능 - 자신이 원하는 안건을 제안하는 기능
    function setProposal(string memory _nameOfProposal, string calldata _detail) public requireIsUser{
        require(keccak256(abi.encodePacked(proposalM[_nameOfProposal].subject)) != keccak256(abi.encodePacked(_nameOfProposal)),"This proposal subject already exist"); // string 을 비교할 때는 kecakk256 , abi.encodedpacked 사용.
        proposalM[_nameOfProposal] = proposal(++proposalCount, _nameOfProposal, _detail, msg.sender,0,0, proStatus.ongoing);
    }

    // - 제안한 안건 확인 기능 - 자신이 제안한 안건에 대한 현재 진행 상황 확인기능 - (번호, 제목, 내용, 찬반 반환 // 밑의 심화 문제 풀었다면 상태도 반환)
    function checkMyProposal(string calldata _nameOfProposal) public view returns (proposal memory){
        require(proposalM[_nameOfProposal].by == msg.sender,"You are not propose it.");
        return proposalM[_nameOfProposal];
    }

    // - 전체 안건 확인 기능 - 제목으로 안건을 검색하면 번호, 제목, 내용, 제안자, 찬반 수 모두를 반환하는 기능
    function getAllProposal(string calldata _nameOfProposal) public view returns(proposal memory){
        return proposalM[_nameOfProposal]; // 안건의 제목만 입력한다면, 누구나 확인가능.
    }

    //- 투표 종료 - 투표 진행중, 통과, 기각 상태를 구별하여 알려주고 전체의 70% 그리고 투표자의 66% 이상이 찬성해야 통과로 변경, 둘 중 하나라도 만족못하면 기각
    function completeProposal(string calldata _nameOfProposal) public {
        require(proposalM[_nameOfProposal].by == msg.sender,"You are not propose it.");
        if(proposalM[_nameOfProposal].accept + proposalM[_nameOfProposal].deny >= userCount * 7/10 && proposalM[_nameOfProposal].accept >= (proposalM[_nameOfProposal].accept + proposalM[_nameOfProposal].deny) * 66/100 ){
            proposalM[_nameOfProposal].status = proStatus.accepted;
        } else{
            proposalM[_nameOfProposal].status = proStatus.rejected;
        }
    }
}

0개의 댓글