문제 풀기 솔리디티 분석 - 1

Lumi·2021년 12월 8일
0
post-thumbnail

🔥 기본값 설정

    uint constant MAX_CASE = 2;
    uint constant MIN_BET = 0.01 ether;
    uint constant MAX_BET = 10 ether;
    uint constant HOUSE_FEE_PERCENT = 5;
    uint constant HOUSE_MIN_FEE = 0.005 ether;

    address public owner;
    uint public lockedInBets;

    struct Bet {
        uint amount;
        uint8 numOfBetBit;
        uint placeBlockNumber;
        uint8 mask;
        address gambler;
    }

    mapping (address => Bet) bets;

    event Reveal(uint reveal);
    event Payment(address indexed beneficiary, uint amount);
    event FailedPayment(address indexed beneficiary, uint amount);

    constructor () public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner, "오너만 실행 가능합니다.");
        _;
    }

기본적인 변수들은 변수명을 알 수 있다고 생각을 하기 떄문에 생략을 하겠습니다.

우리가 0.01의 값을 가지고 있지만 uint로 선언 가능한 이유는 원래 단위가 wei이기 떄문입니다.

struct 구조체는 얼마만큼의 금액을 배팅하였는지에 해당하는 구조체 입니다.

이 코드의 목표는 금액을 걸고 문제를 풀 시에 가져가는 것이 이기 때문에 구조체를 통해서 값을 저장할 객체를 만들어 줍니다.

이후 컨트랙트가 형성될떄 기본적으로 owner변수를 컨트랙트 실행자로 바꾸어 주고

onlyOwner이라는 modifier를 만들어 주어 오로지 컨트랙트 생성자만 실행 가능하게 만들어 줍니다.

🔥 withdrawFunds

설명에는 이더를 인출하는 함수로 작성을 하였지만 실제로는 단순히 이벤트를 작동시키는 역할을 하게 됩니다.

function withdrawFunds(address beneficiary, uint withdrawAmount) external onlyOwner{
        require(withdrawAmount + lockedInBets <= address(this).balance, "금액보다 많습니다.");
        sendFunds(beneficiary, withdrawAmount);
    }

function sendFunds(address beneficiary, uint amount) private {
    if(beneficiary.send(amount)){
        emit Payment(beneficiary, amount);
    } else{
        emit FailedPayment(beneficiary, amount);
    }
}

이처럼 함수가 실행이 되면 일단 address(this).balance의 값은 현재 컨트랙트에 저장된 금액을 말합니다.

즉 인출할 금액과 배팅한 총 금액이 컨트랙트에 있는 금액보다 적을때에만 통과가 이루어 집니다.

  • 특정 문제가 없다면 통과하는 require조건 입니다.

🔥 Kill

function kill() external onlyOwner {
        require(lockedInBets == 0, "맡겨둔 금액이 있습니다.");
        selfdestruct(owner);
}

function () public payable{}

이 함수가 하는 역할은 많이 없습니다.

배팅된 금액이 없다는 가정하에만 실행이 가능하고 selfdestruct라는 방법을 이용함으로써 컨트랙트 코드를 블록체인 네트워크에서 삭제 가능합니다.

특정 회사가 폐업을 하는것과 같습니다.

🔥 placeBet

function placeBet(uint8 betMask) external payable{
    uint amount = msg.value;

    require(amount >= MIN_BET && amount <= MAX_BET, "정해진 범위에서만 배팅 가능합니다.");
    require(betMask > 0 && betMask < 256, "배팅 가능한 금액이 정해져 있습니다.");

    Bet storage bet = bets[msg.sender];

    // null인지 체크하는 부분
    require (bet.gambler == address(0),"계좌 오류");
    

    uint8 numOfBetBit = countBits(betMask);

    bet.amount = amount;
    bet.numOfBetBit = numOfBetBit;
    bet.placeBlockNumber = block.number;
    bet.mask = betMask;
    bet.gambler = msg.sender;

    uint possibleWinningAmount = getWinningAmount(amount, numOfBetBit);

    lockedInBets += possibleWinningAmount;

    require(lockedInBets < address(this).balance, "배팅된 금액보다 큽니다.");
}

function countBits(uint8 _num) internal pure returns (uint8) {
        uint8 count;
        while(_num >0){
            count+= _num & 1;
            _num >>= 1;
        }
        return count;
}

function getWinningAmount(uint amount, uint8 numOfBetBit) private pure returns (uint winningAmount) {
        require (0 < numOfBetBit && numOfBetBit <MAX_CASE);

        uint houseFee = amount * HOUSE_FEE_PERCENT / 100;

        if(houseFee < HOUSE_MIN_FEE){
            houseFee = HOUSE_MIN_FEE;
        }

        uint reward = amount / (MAX_CASE + (numOfBetBit - 1));

        winningAmount = (amount - houseFee) + reward;
}

이제 게임을 배팅을 하는 역할을 하는 함수 입니다.

payable함수 이기 떄문에 기본적으로 msg.value를 사용가능하며 이는 배팅된 금액을 이야기 합니다.

지정한 금액에 어울리는 금액을 배팅해야하며 인자로 받는 금액 또한 지정된 범위에서 인자를 지정해야 합니다.

너무 과한 배팅으로 게임을 망치는 행위를 막기 위함입니다.

이후 bets라는 mapping된 값을 새롭게 설정을 하고

또다시 require를 통해서 해당 계정이 null아닌지를 확인 합니다.

그후 값을 수정하면 됩니다.

numOfBetBit 부분은 solidity연산자를 활용하는 면에서 아직 부족하기 떄문에 제대로 이해는 하지 못했습니다 ㅠㅠ

possibleWinningAmount 승리하였을시 가져갈수 잇는 배팅 금액을 말합니다.

  • 즉 그 값을 locked변수에 갱신시킴으로써 후에 가져가게 됩니다.

🔥 revealResult

function revealResult(uint8 seed) external {
        Bet storage bet = bets[msg.sender];
        uint amount = bet.amount;
        uint8 numOfBetBit = bet.numOfBetBit;
        uint placeBlockNumber = bet.placeBlockNumber;
        address gambler = bet.gambler;

        require(amount>0);

        require(block.number > placeBlockNumber);

        //난수ㅡㄹ 생성
        bytes32 random = keccak256(abi.encodePacked(blockhash(block.number-seed), blockhash(placeBlockNumber)));
 
        uint reveal = uint(random) % MAX_CASE;

        uint winningAmount = 0;
        uint possibleWinningAmount = 0;
        possibleWinningAmount = getWinningAmount(amount, numOfBetBit);

        if((2**reveal) & bet.mask != 0){
            winningAmount = possibleWinningAmount;
        }

        emit Reveal(2**reveal);

        if(winningAmount>0){
            sendFunds(gambler, winningAmount);
        }

        lockedInBets -= possibleWinningAmount;
        clearBet(msg.sender);
}
    
    function clearBet(address player) private{
        Bet storage bet = bets[player];

        if(bet.amount >0 ){
            return;
        }

        bet.amount = 0;
        bet.numOfBetBit = 0;
        bet.placeBlockNumber = 0;
        bet.mask = 0;
        bet.gambler = address(0);
    
    }

게임이 끝난뒤에 이제 결과를 내는 함수 입니다.

기본적인 변수들을 할당해 준뒤 block의 높이를 확인합니다.
확인을 하는 이유는 혹시라도 게임이 이루어 지기 전에 결과 함수가 먼저 작동을 하여 네트워크에 게임 순서대로 기록이 남지 않게 하기 위함입니다.

이후 난수를 생성합니다.

  • 이 부분은 저도 아직 잘 모르겠네요 ㅠ

이후는 개발자의 입맛에 맞게 적당한 값을 도출하면 됩니다.

clearBet 함수는 설정값들을 초기화 하는 함수입니다.

🔥 후기

정말 간단하게 알아보아서 혹시라도 글을 읽는분들이 이해가 가셨는지는 모르겠네요..

솔리디티 코드 자체는 많이 어려운 편이 아니고 개인적으로는 얼추 적어도 가독성이 매우 뛰어나다고 생각을 하여 많은 설명은 하지 않았습니다.

profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글