이번 포스팅에서는 스마트 컨트랙트 함수에서 사용되는 payable 속성에 대해 알아보고자 한다.
Solidity 언어는 코인(coin) 혹은 토큰(token)이라는 가상화폐를 다루는 언어이다. 다른 언어들은 프로그램을 만들기 위한 언어로 탄생했지만 Solidity는 가상화폐라는 돈을 다루기 위한 언어로 탄생했기 때문에 다른 언어와는 다르게 payable이라는 키워드가 존재한다.
Solidity 언어에서 payable 키워드는 이더리움 플랫폼 위에서 이더(ether)를 전송하는 스마트 컨트랙트를 작성하기 위해 반드시 사용돼야 하는 키워드이다. 즉, payable을 작성한 함수에서만 이더(ether)를 보낼 수 있고 payable을 작성하지 않은 함수에서는 이더(ether)를 보낼 수 없다. 코드를 살펴보면서 좀 더 이해해보도록 하자.
AppleShop 이라는 스마트 컨트랙트를 다음과 같이 만들었다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract AppleShop {
mapping(address => uint) myApple;
// payable 속성이 있을 때 CA는 ETH를 받을 수 있는 상태가 된다.
// tx 객체의 value값에 ETH를 넣을 수 있다.
function buyApple() public payable {
myApple[msg.sender] += 1;
}
function sellApple(uint _applePrice) public payable {
uint256 refund = myApple[msg.sender] * _applePrice;
myApple[msg.sender] = 0;
// payable() : address 타입을 인자값으로 전달
payable(msg.sender).transfer(refund); // msg.sender 계정에 돈을 보내는 행위
}
function getApple() view public returns (uint) {
return myApple[msg.sender];
}
}
buyApple( ) 함수와 sellApple( ) 함수에 payable 속성이 추가된 것을 확인할 수 있는데 payable이 작성된 함수의 경우 해당 함수를 실행시키는 트랜잭션을 발생시킬 때 CA(contract address)에게 이더(ether)를 전송할 수 있게 된다. payable 속성이 있을 때 CA는 이더(ether)를 받을 수 있는 상태가 되고 CA에 사용자의 이더를 잠시 보관해 놓는 개념으로 사용하게 된다.
실제 위에서 작성한 스마트 컨트랙트가 어떻게 동작하는지 확인하기 위해 ganache로 로컬 이더리움 네트워크를 생성하였으며 truffle을 이용해 해당 네트워크에 스마트 컨트랙트를 배포하였다. React를 사용해 프론트 화면을 구성하였으며 메타마스크와 프론트를 연결하여 트랜잭션을 발생시킬 수 있도록 하였다. 아래의 코드는 프론트 쪽 코드이다.
// useWeb3 훅
import { useEffect, useState } from 'react';
import Web3 from 'web3/dist/web3.min.js';
const useWeb3 = () => {
const [account, setAccount] = useState();
const [web3, setWeb3] = useState();
const getRequestAccount = async () => {
const [account] = await window.ethereum.request({
method: 'eth_requestAccounts',
});
return account;
};
useEffect(() => {
(async () => {
const account = await getRequestAccount();
const web3 = new Web3(window.ethereum);
setAccount(account);
setWeb3(web3);
})();
}, []);
return [web3, account];
};
export default useWeb3;
// App.js 파일
import React from 'react';
import './App.css';
import useWeb3 from './hooks/useWeb3';
import AppleShop from './components/AppleShop';
function App() {
const [web3, account] = useWeb3();
if (!account) return <h1>메타마스크 연결 이후 사용해주세요</h1>;
return (
<div className="App">
<h2>사과앱</h2>
<AppleShop web3={web3} account={account} />
</div>
);
}
export default App;
// AppleShop 컴포넌트 (AppleShop.jsx 파일)
import React, { useEffect, useState } from 'react';
import AppleShopContract from '../contracts/AppleShop.json';
const AppleShop = ({ web3, account }) => {
const [apple, setApple] = useState();
const [deployed, setDeployed] = useState();
const buy = async () => {
await deployed.methods.buyApple().send({
from: account,
to: '0x660ec2062AE3523505973c1651C62Fd9c9Af2413',
value: web3.utils.toWei('1', 'ether'),
});
};
const sell = async () => {
const eth = web3.utils.toWei('1', 'ether');
await deployed.methods.sellApple(eth).send({
from: account,
to: '0x660ec2062AE3523505973c1651C62Fd9c9Af2413',
});
};
useEffect(() => {
(async () => {
if (!web3) return;
// 인자값 2개
// abi , CA
const instance = await new web3.eth.Contract(
AppleShopContract.abi,
'0x660ec2062AE3523505973c1651C62Fd9c9Af2413',
);
const currentApple = await instance.methods.getApple().call();
setApple(currentApple);
setDeployed(instance);
})();
}, []);
return (
<div>
<div>사과 가격 : 1 ETH</div>
<div>내가 가진 사과 : {apple}</div>
<button onClick={buy}>구매하기</button>
<div>사과 판매 가격 : {apple * 1} ETH</div>
<button onClick={sell}>환불</button>
</div>
);
};
export default AppleShop;
구매하기 버튼을 클릭했을 때 buy 함수가 실행되며 buy 함수에 의해 스마트 컨트랙트 안에 존재하는 buyApple( ) 함수를 실행시키는 트랜잭션이 발생된다. 이때 buyApple( ) 함수는 payable 이기 때문에 트랜잭션을 발생시킬 때 CA 에게 이더(ether)를 전송할 수 있다. send( ) 메소드의 인자값으로 전달하는 객체 안에 value 속성을 추가하여 얼마만큼의 이더를 CA에게 전송할 것인지 명시해주기만 하면 된다.
const buy = async () => {
await deployed.methods.buyApple().send({
from: account,
to: '0x660ec2062AE3523505973c1651C62Fd9c9Af2413',
value: web3.utils.toWei('1', 'ether'),
});
};
위와 같이 CA 에게 1 ETH가 전송됨과 동시에 buyApple( ) 함수가 실행된다. 실제 truffle console을 통해 CA의 balance를 조회해보면 다음과 같이 1 ETH 만큼의 잔액이 조회되는 것을 확인할 수 있다. (트랜잭션을 발생시킨 계정으로부터 1 ETH를 받은 것)
구매 버튼을 몇번 더 눌러 트랜잭션을 발생시킨 계정이 3 개의 사과를 가지고 있게 되었다. 그리고 buyApple( ) 함수를 실행시킬 때마다 CA에게는 1 ETH를 전송하여 CA 에는 현재 3 ETH 만큼의 잔액이 존재하고 있는 상황이다.
이제 환불 버튼을 눌러 스마트 컨트랙트 상에 존재하는 sellApple( ) 함수를 실행시키면 payable( msg.sender ).transfer( refund )에 의해 CA에 존재하던 이더(ether)들이 트랜잭션을 발생시킨 계정으로 전송된다. CA의 잔액은 0이 되고 msg.sender 에 해당하는 계정의 balance는 CA에 존재하는 이더(ether) 만큼이 가산되었다.
payable 속성이 들어간 함수를 실행시키는 간단한 스마트 컨트랙트 예제를 살펴보았는데 이더(ether)를 전송하는 기능이 있는 스마트 컨트랙트를 작성할 때는 반드시 payable 키워드를 활용하여 컨트랙트 안의 함수들을 작성해야 함을 명심하도록 하자.
'Ethereum' 카테고리의 다른 글
Ethereum/이더리움 - OpenZeppelin / 토큰 컨트랙트 / 스왑 컨트랙트 (2) | 2022.07.23 |
---|---|
Ethereum/이더리움 - 인터페이스 & ERC-20 / 토큰 발행하기 (0) | 2022.07.22 |
Ethereum/이더리움 - 스마트 컨트랙트로 투표 Dapp 만들기 (0) | 2022.07.18 |
Ethereum/이더리움 - 스마트 컨트랙트로 토큰 발행하기 (0) | 2022.07.17 |
Ethereum/이더리움 - 스마트 컨트랙트 이벤트 등록 및 백엔드에서 트랜잭션 생성하기 (0) | 2022.07.14 |