Today
Total
09-14 07:26
관리 메뉴

devGYU World

[Solidity] 스마트 컨트랙트 간편 예제(자판기) 본문

etc/solidity

[Solidity] 스마트 컨트랙트 간편 예제(자판기)

devGYU 2022. 7. 18. 16:39

솔리디티를 통해 간단한 예제를 작성하고자 한다.

자판기 시스템을 구현할 것이며 코드는 아래의 조건을 충족해야 한다.

 

1. 음료수의 초기 수량은 10개이다.

2. 음료수의 가격은 1 eth 로 고정한다.

3. 음료수가 모두 매진되면 "Sold out" 이벤트가 발생한다.

4. 처음 컨트랙트를 배포한 관리자 주소만 음료수 재고를 조정 가능하다.

5. 관리자 주소만 현재 음료수의 재고를 확인할 수 있다.

6. 관리자 주소만 현재 자판기에 입금된 잔액(balance)를 확인하고 인출할 수 있다.

 

상기의 조건을 만족하는 전체 코드는 아래와 같다.

 

// SPDX-License-Identifier: MIT License
pragma solidity >=0.7.0 <0.9.0;

contract VendingMachine {
    address owner;
    string[] itemList = ["Apple Juice", "Grape Juice", "Coke", "Water"];
    mapping(string => uint8) itemStock;

    event SomeoneBuy(string itemName, uint8 stock);
    event SoldOut(string itemName);
    event AddItemStock(string itemName, uint8 stock);

    constructor() {
        owner = msg.sender;

        for (uint8 i = 0; i < itemList.length; i++) {
            itemStock[itemList[i]] = 10;
        }
    }

    modifier onlyOwner() {
        require(owner == msg.sender, "Only Owner!!");
        _;
    }

    function buyItem(uint8 _index) public payable returns (string memory) {
        require(msg.value == 1 ether, "Price is 1 eth per piece!");

        if (itemStock[itemList[_index]] > 0) {
            itemStock[itemList[_index]]--;
            emit SomeoneBuy(itemList[_index], itemStock[itemList[_index]]);
        } else if (itemStock[itemList[_index]] == 0) {
            emit SoldOut(itemList[_index]);
            revert("Sold out!!");
        }

        return "Success!";
    }

    function addStock(uint8 _index, uint8 _num)
        public
        onlyOwner
        returns (uint8 currentStock)
    {
        itemStock[itemList[_index]] += _num;
        currentStock = itemStock[itemList[_index]];

        emit AddItemStock(itemList[_index], currentStock);

        return currentStock;
    }

    function checkStock(uint8 _index)
        public
        view
        onlyOwner
        returns (string memory itemName, uint8 InStock)
    {
        itemName = itemList[_index];
        InStock = itemStock[itemList[_index]];
        return (itemName, InStock);
    }

    function checkTotalBalance() public view onlyOwner returns (uint256) {
        return address(this).balance;
    }

    function withdrawBalance() public onlyOwner {
        (bool success, ) = payable(msg.sender).call{
            value: address(this).balance
        }("");
        require(success, "Failed to withdraw balance!");
    }
}

 

코드의 각 부분이 어떻게 구성되어 있는지 설명하자면,

 

우선 컨트랙트를 배포하는 관리자 주소를 저장할 owner를 선언한다. 후에 modifier에서 사용될 것이다.

자판기에 있는 음료의 종류를 배열에 선언해둔다.

배열에 선언된 각 음료수의 재고 수량을 mapping 해둔다.

address owner; // 스마트컨트랙트를 배포하는 관리자
string[] itemList = ["Apple Juice", "Grape Juice", "Coke", "Water"]; // 자판기의 재고
mapping(string => uint8) itemStock; // 자판기에 있는 각 재고마다의 수량

 

컨트랙트가 수행되며 발생되는 event를 정의한다.

SomoneBuy: 누군가 음료를 구매하면 판매된 음료와 남은 재고를 알림.

SoldOut: 품목의 재고가 모두 소진되었음을 알림.

AddItemStock: 지정한 품목의 재고를 추가함을 알림.

 

event SomeoneBuy(string itemName, uint8 stock);
event SoldOut(string itemName);
event AddItemStock(string itemName, uint8 stock);

 

constructor로 owner 주소를 저장하고 반복문을 통해서 mapping 된 재고의 수량을 초기 10개씩으로 설정한다.(1번 조건)

modifier를 통해 각 함수 중 관리자만 접근 가능한 제어자를 생성한다. (4~6번 조건)

constructor() {
        owner = msg.sender;

        for (uint8 i = 0; i < itemList.length; i++) {
            // 음료 재고를 10개씩 추가함
            itemStock[itemList[i]] = 10;
        }
}

modifier onlyOwner() {
        require(owner == msg.sender, "Only Owner!!");
        _;
}

 

음료를 구매하는 함수를 작성한다.

이때의 조건으로는 각 음료의 가격인 1 eth가 컨트랙트에 입금되어야 하며(2번 조건), 음료 구매가 완료될 때마다 재고가 1개씩 줄어들 것이다. 최종적으로 음료가 모두 소진되면 "Sold out" 이벤트가 발생하며 해당 재고에 대한 판매가 중단된다.(3번 조건)

 

function buyItem(uint8 _index) public payable returns (string memory) {
	// 1 eth가 입금되어야 구매가 가능함
        require(msg.value == 1 ether, "Price is 1 eth per piece!");

        if (itemStock[itemList[_index]] > 0) {
            // 구매가 완료되면 재고 수량이 1씩 감소
            itemStock[itemList[_index]]--;
            emit SomeoneBuy(itemList[_index], itemStock[itemList[_index]]);
        } else if (itemStock[itemList[_index]] == 0) {
            // 재고가 모두 소진되면 Sold out 이벤트가 발생하고 코드를 종료함
            emit SoldOut(itemList[_index]);
            revert("Sold out!!");
        }

        return "Success!";
    }

 

관리자만 수행가능한 함수를 작성한다.

 

addStock: 지정한 품목에 대해서 입력한 수량만큼 재고를 더한다.

checkStock: 지정한 품목에 대한 재고 수량을 확인한다.

checkTotalBalance: 자판기에 입금된 총액을 확인한다.

withdrawBalance: 자판기에 임금된 금액을 모두 인출한다.

    function addStock(uint8 _index, uint8 _num)
        public
        onlyOwner
        returns (uint8 currentStock)
    {
        itemStock[itemList[_index]] += _num;
        currentStock = itemStock[itemList[_index]];

        emit AddItemStock(itemList[_index], currentStock);

        return currentStock;
    }

    function checkStock(uint8 _index)
        public
        view
        onlyOwner
        returns (string memory itemName, uint8 InStock)
    {
        itemName = itemList[_index];
        InStock = itemStock[itemList[_index]];
        return (itemName, InStock);
    }

    function checkTotalBalance() public view onlyOwner returns (uint256) {
        return address(this).balance;
    }

    function withdrawBalance() public onlyOwner {
        (bool success, ) = payable(msg.sender).call{
            value: address(this).balance
        }("");
        require(success, "Failed to withdraw balance!");
    }

 

이렇게 100줄 미만의 간단한 스마트 컨트랙트 코드를 솔리디티로 작성해보았다.

 

물론 더 다양한 조건들을 사용자가 추가하는 것도 가능할 것이다.

ex) 최대 재고 수량 한계 정하기, 재고가 소진될 때만 재고 추가가 가능한 조건문 추가하기 등 ...

Comments