스마트컨트랙트 기본세팅
// 위의 작업 후 이어진 작업
** 최상위 폴더 work
// 현 경로 /work/contracts
pragma solidity >=0.4.21 <0.7.0;
contract SimpleStorage {
uint storedData;
event Change(string message, uint newVal);
// 두 가지 인자를 받는 이벤트 선언
function set(uint x) public {
storedData = x;
emit Change("set",x);
// 내용을 넣자마자 화면에 보여주는 작업
}
function get() publie view returns (uint) {
returns storedData;
}
}
// 현 경로 /work/client/src/contracts
// rm Migrations.json && rm testStorage.json ==> 기존에 존재하던 두 파일 삭제
// rm => 파일이나 폴더 삭제해주는 명령어
truffle compile ->솔리디티 파일 컴파일
truffle migrate -> 배포해줌
// 하려는 작업에 따라 App.js 내용 달라짐
// 나는 서명에 대한 작업을 할 것임.
사전에 설치
-- axios
// 우선 전체 코드
// 함수형으로 코드 작성
import React,{useEffect, useState, useReducer} from 'react';
import TestStorageContract from './contracts/testStorage.json';
import getWeb3 from './getWeb3';
import axios from 'axios';
const reducer = (state,action) => {
switch(action.type){
case "INIT":
let {web3,Instance,account} = action
return{
...state,
web3,
Instance,
account
}
}
}
const INIT_ACTIONS = (web3,Instance,account) => {
return{
type:'INIT',
web3,
Instance,
account
}
}
const App = () => {
const initialState = {
web3:null,
Instance:null,
account:null
}
const [value,setValue] = useState(0);
const [loadding,setLoadding] = useState(false);
const [storage,setStorage] = useState(0);
const [state,dispatch] = useReducer(reducer,initialState);
const handleResult = (log,web3) => {
const params = [
{type:'string',name:'message'},
{type:'uint256',name:'newVal'}
]
//decodeLog() --> 2개의 인자값
// 첫번째 --> 데이터 형식
// 두번째 --> log.data
const returnValues = web3.eth.abi.decodeLog(params,log.data);
console.log('returnValue',returnValues)
setStorage(returnValues.newVal);
setLoadding(prev=>!prev)
}
const handleChange = (e) => {
let val = e.target.value;
setValue(val);
}
// 일반서명-메타마스크를 통해 이루어지는 것.
const send = async () => {
const {account, Instance} = state;
if(value > 0){
setLoadding(prev=>!prev)
await Instance.set(value,{from:account})
// 비동기적 처리
}else if(value < 0) {
setStorage('양심 있냐?')
}
}
/*
1. backend에 요청
2. backend에서 rawTx 객체를 반환
3. 반환받은 값으로 sendTransaction()을 실행 (실질적 서명이 이루어짐)
*/
// SERVER거친 서명 //http://localhost:3001/rpc/set
const sendAPI = async () => {
const {web3,account} = state;
if(value > 0 ){
setLoadding(prev=>!prev);
let result = await axios.post('http://localhost:3001/rpc/set',{from:account,val:value});
console.log(result.data.rawTx,'왜 안나와?');
if(result.data !== undefined &&
result.data.rawTx !== undefined &&
result.data.success == true ){
await web3.eth.sendTransaction(result.data.rawTx);
/*
rawTx = {
"from":"address",
"to":...,
"data":"실질적 데이터 부분 들어감",
"gasLimit":"",
"gasPrice":""
}
*/
}
}
}
// SERVER서명
const sendTx = async () => {
const {account} = state;
if(value > 0){
setLoadding(prev=>!prev);
const result = await axios.post('http://localhost:3001/rpc/setTx',{from:account,val:value})
console.log(result)
}
}
// instance 중요 ============================================
const init = async () => {
const contract = require('@truffle/contract');
const web3 = await getWeb3();
// 위의 반환값은 프로미스 객체이므로 await로 받음 // 함수 앞에 async명시해줌
const [account] = await web3.eth.getAccounts();
// 배열이므로 구조분해 할당문으로 해서 받음.
//networkID도 가져올 수 있다.
//const netwrokID = await web3.eth.net.getId();
let testStorage = contract(TestStorageContract);
testStorage.setProvider(web3.currentProvider);
const Instance = await testStorage.deployed();
// instance생성하는 부분 ** 매우 중요 **
// 배포한 코드까지 접근할 수 있도록, Instance 코드로 만든 것.
console.log(Instance,"Instance");
dispatch(INIT_ACTIONS(web3,Instance,account))
// sol파일에서 emit과 관련한 부분.
web3.eth.subscribe('logs',{address:Instance.address})
// 두 번째 인자값에는 배포가 된 계정의 address
.on("data",log=>{
console.log(log,'log');
// transaction이 발생할 때마다 로그가 찍힘.
handleResult(log,web3); // handleResult(log,web3)부분 잘 이해안감... 밑에 확인
/*
const handleResult = (log,web3) => {
const params = [
{type:'string',name:'message'},
{type:'uint256',name:'newVal'}
]
//decodeLog() --> 2개의 인자값
// 첫번째 --> 데이터 형식
// 두번째 --> log.data
const returnValues = web3.eth.abi.decodeLog(params,log.data);
console.log('returnValue',returnValues)
setStorage(returnValues.newVal);
setLoadding(prev=>!prev)
}
*/
})
.on('error',err=>console.log(err,'err'));
}
useEffect(()=>{
init()
},[])
return(
<>
<div>
<input type="text" value={value} onChange={handleChange}/>
<div>
<button onClick={send}>일반서명</button>
<button onClick={sendAPI}>SERVER거치고 서명</button>
<button onClick={sendTx}>SERVER서명</button>
</div>
<div>
{loadding?'loadding':storage}
</div>
</div>
</>
)
}
export default App;
// 최상위 폴더인 work에서 server폴더 생성
//server.js 파일 내용
const express = require('express');
const app = express();
const router = require('./routes');
const cors = require('cors');
const bodyParser = require('body-parser');
const web3 = require('web3')
app.use(cors());
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.use('/',router);
app.listen(3001,()=>{
// 프론트인 리엑트에서 3000번 포트를 사용 중이므로 3000번 포트 사용할 수 없음.
console.log('server start port 3001')
})
// 나머지 index.js파일은 단순히 라우터 기능을 하는 부분이므로 코드 생량
// 실질적인 기능을 하는 파일은 rpc.controller.js파일
// rpc.controller.js 파일 내용
const Web3 = require('web3');
const web3 = new Web3('http://localhost:7545');
const abi = require('../../../client/src/contracts/testStorage.json').abi;
const {address} = require('../../../client/src/contracts/testStorage.json').networks["5777"];
const ethTx = require('ethereumjs-tx').Transaction;
// SERVER를 거친 서명 //http://localhost:3001/rpc/set
const set = async (req,res) => {
const {from,val} = req.body;
const instance = await new web3.eth.Contract(abi,address);
console.log(instance,'이건 instance ');
const data = await instance.methods.set(val).encodeABI();
console.log(data,'이건 data')
let txObject = {
nonce:null,
from,
to:address,
data,
gasLimit:web3.utils.toHex(30000),
gasPrice:web3.utils.toHex(web3.utils.toWei('20','gwei'))
}
res.json({
success:true,
rawTx:txObject
})
}
// SERVER자체 서명 //http://localhost:3001/rpc/setTx
const setTx = async (req,res) => {
const {from,val} = req.body;
const instance = await new web3.eth.Contract(abi,address);
const data = await instance.methods.set(val).encodeABI();
const txCount = await web3.eth.getTransactionCount(from);
const privateKey = Buffer.from('/* 비밀키 */','hex');
let txObject = {
nonce:web3.utils.toHex(txCount),
from,
to:address,
data,
gasLimit:web3.utils.toHex(3000000),
gasPrice:web3.utils.toHex(web3.utils.toWei('20','gwei'))
}
const tx = new ethTx(txObject);
tx.sign(privateKey);
const serializedTx = tx.serialize();
const txhash = await web3.eth.sendSignedTransaction('0x'+serializedTx.toString('hex'));
res.json({
success:true,
rawTx:txObject,
txhash
})
}
module.exports = {
set,
setTx
}
참고
// /work/contracts폴더내의 testStoraged.sol파일 내용
pragma solidity >=0.4.22 <0.9.0;
contract testStorage {
uint storedData;
event Change(string message, uint newVal);
function set(uint x) public {
storedData = x;
emit Change("set",x);
}
function get() public view returns (uint) {
return storedData;
}
}