npx truffle init
contracts/Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
uint256 value;
constructor() {}
function getValue() public view returns(uint256) {
return value;
}
function increment() public {
value += 1;
}
function decrement() public {
value -= 1;
}
}
contract Hello {
string public hi = 'Hello Solidity';
}
truffle-config.js
solc 버전 수정Ganache CLI v6.12.2 (ganache-core: 2.13.2) 기준으로 solc 버전이 0.8.18 이하일 때만 컴파일한 파일을 배포할 때 오류가 나지 않는다.
npx truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/Counter.sol
> Artifacts written to /home/cloudcoke/my/blockchain/etherium/230524_p/build/contracts
> Compiled successfully using:
- solc: 0.8.18+commit.87f61d96.Emscripten.clang
Counter.json
, Hello.json
확인컴파일을 진행하면 contract 마다 json 파일이 생성된다.
이 파일에 배포할 때 필요한 것들이 들어있다.
truffle-config.js
에서 networks를 설정해줘야 한다.ganache를 이용해서 로컬 환경에서 배포를 진행하기 위해 development 부분을 주석해제를 해준다.
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
}
migration : 마이그레이션은 Ethereum 네트워크에 계약을 배포하는 데 도움이 되는 JavaScript 파일
migrations/1_deploy_counter.js
마이그레이션이 성공적으로 실행되었는지를 기록하기 위해 파일 이름 맨 앞에 번호를 적어준다.
const Counter = artifacts.require("Counter")
const Hello = artifacts.require("Hello")
// require의 인자로 solidity 파일 이름이 아닌 계약 정의 이름을 전달해 주어야 한다.
// 컴파일을 진행한 뒤 생성된 json 파일의 이름을 전달해 주면 된다.
module.exports = (developer) => {
developer.deploy(Counter)
developer.deploy(Hello)
}
// 매개변수로 받는 developer는 truffle이 배포를 위해 노드에 요청을 날릴 때 해당 노드에 존재하는 EOA 중 첫 번째 EOA를 넣어준다고 볼 수 있다.
npx ganache-cli -h 0.0.0.0
npx truffle migration
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'development'
> Network id: 1684908856831
> Block gas limit: 6721975 (0x6691b7)
1_deploy_counter.js
===================
Deploying 'Counter'
-------------------
> transaction hash: 0x12a6d666b307bfbe301fbe9e3e1a25a5a8289a26e90afb6b18a6b5760b9f0582
> Blocks: 0 Seconds: 0
> contract address: 0x25Aee7d838028EDE29c5aAa10eFF614aEeab1711
> block number: 1
> block timestamp: 1684909164
> account: 0xd48b29AD5361057027A22ECb266A99135600dBFD
> balance: 99.99704426
> gas used: 147787 (0x2414b)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00295574 ETH
Deploying 'Hello'
-----------------
> transaction hash: 0x723e1df7b248506ea835b1a9d473cd59e36867ba8e679da244a4db11349ec367
> Blocks: 0 Seconds: 0
> contract address: 0x081E64644144029988f7e49bA20EfEbaF321130f
> block number: 2
> block timestamp: 1684909164
> account: 0xd48b29AD5361057027A22ECb266A99135600dBFD
> balance: 99.9929363
> gas used: 205398 (0x32256)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00410796 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.0070637 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.0070637 ETH
ganache의 결과를 보면 두 개의 블록이 생성된 걸 확인할 수 있다.
eth_sendTransaction
Transaction: 0x12a6d666b307bfbe301fbe9e3e1a25a5a8289a26e90afb6b18a6b5760b9f0582
Contract created: 0x25aee7d838028ede29c5aaa10eff614aeeab1711
Gas usage: 147787
Block Number: 1
Block Time: Wed May 24 2023 15:19:24 GMT+0900 (Korean Standard Time)
eth_sendTransaction
Transaction: 0x723e1df7b248506ea835b1a9d473cd59e36867ba8e679da244a4db11349ec367
Contract created: 0x081e64644144029988f7e49ba20efebaf321130f
Gas usage: 205398
Block Number: 2
Block Time: Wed May 24 2023 15:19:24 GMT+0900 (Korean Standard Time)
console로 테스트를 진행해 볼 수 있다.
npx truffle console
Counter.address
# '0x25Aee7d838028EDE29c5aAa10eFF614aEeab1711'
Hello.address
# '0x081E64644144029988f7e49bA20EfEbaF321130f'
Counter.deployed().then(instance => counter = instance)
window 객체에 counter라는 변수를 만들어 Counter의 인스턴스 값을 넣어준다.
counter.getValue()
BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }
Big Number 타입의 결과물이 나온다.
counter.increment()
{
tx: '0x9cca4cf59fbe9fb65401fae43d725b3d4d33f5e710764478770c180d9997ecd6',
receipt: {
transactionHash: '0x9cca4cf59fbe9fb65401fae43d725b3d4d33f5e710764478770c180d9997ecd6',
transactionIndex: 0,
blockHash: '0x880fd811422252021bdbea7719f0f6c434ad09d142869b523ff2021ff333c821',
blockNumber: 3,
from: '0xd48b29ad5361057027a22ecb266a99135600dbfd',
to: '0x25aee7d838028ede29c5aaa10eff614aeeab1711',
gasUsed: 42245,
cumulativeGasUsed: 42245,
contractAddress: null,
logs: [],
status: true,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: []
},
logs: []
}
트랜잭션 내용이 나온다.
counter.getValue()
value 값을 다시 조회하면 1이 증가해 있는 걸 확인할 수 있다.
BN { negative: 0, words: [ 1, <1 empty item> ], length: 1, red: null }
truffle을 이용해 smart contract를 배포하고 실행 테스트까지 한 다음 웹에서 사용할 수 있도록 한다.
npx create-react-app front
cd front
npm install web3
mkdir src/contracts
cp ../build/contracts/Counter.json src/contracts
src/hooks/useWeb3.jsx
import Web3 from "web3"
import { useEffect, useState } from "react"
const useWeb3 = () => {
const [account, setAccount] = useState(null)
const [web3, setWeb3] = useState(null)
const [netId, setNetId] = useState(null)
const init = async () => {
const [account] = await window.ethereum.request({
method: "eth_requestAccounts",
}) // EOA 가져오기
const web3 = new Web3(window.ethereum)
setNetId(await web3.eth.net.getId()) // 네트워크 ID 가져오기
setAccount(account)
setWeb3(web3)
}
useEffect(() => {
if (!window.ethereum) return
init()
}, [])
return [account, web3, netId]
}
export default useWeb3
src/pages/Counter.jsx
import { useState, useEffect } from "react"
import CounterContract from "../contracts/Counter.json"
const Counter = ({ account, web3, netId }) => {
const [count, setCount] = useState(0)
const [deployed, setDeployed] = useState(null)
const get = async () => {
if (deployed === null) return alert("deployed가 없음")
const value = await deployed.methods.getValue().call()
setCount(value)
}
const increment = async () => {
if (deployed === null) return alert("deployed가 없음")
await deployed.methods.increment().send({
from: account,
})
get()
}
const decrement = async () => {
if (deployed === null) return alert("deployed가 없음")
await deployed.methods.decrement().send({
from: account,
})
get()
}
useEffect(() => {
if (web3 === null || account === null) return
const Deployed = new web3.eth.Contract(CounterContract.abi, CounterContract.networks[netId].address)
setDeployed(Deployed)
Deployed.methods
.getValue()
.call()
.then((value) => setCount(value))
}, [])
return (
<>
<div>
<h2>Counter : {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
</>
)
}
export default Counter
src/App.jsx
import Counter from "./pages/Counter"
import useWeb3 from "./hooks/useWeb3"
const App = () => {
const [account, web3, netId] = useWeb3()
if (!account) return <>메타마스크를 연결하고 사용이 가능합니다.</>
return (
<>
<h1>Counter</h1>
<Counter account={account} web3={web3} netId={netId} />
</>
)
}
export default App