devGYU World
[Solidity] 스마트 컨트랙트 간편 예제(자판기) 본문
솔리디티를 통해 간단한 예제를 작성하고자 한다.
자판기 시스템을 구현할 것이며 코드는 아래의 조건을 충족해야 한다.
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) 최대 재고 수량 한계 정하기, 재고가 소진될 때만 재고 추가가 가능한 조건문 추가하기 등 ...