솔리디티는 절대 복잡하게 코드를 짜지 않는다!
→ 행동이 많고 데이터가 많아지면 가스비가 증가... 웬만하면 솔리디티 안의 내용들은 최대한 간결하게 짜되 조건을 걸어준다 (require
구문을 통해서 조건식을 걸어주고 참일 때 코드 실행, 거짓이면 거부 메시지 보낸다)
가시성 지정자 > internal
아니면 public
키워드를 사용하는 것이 가장 일반적 (internal을 제일 많이 사용하신다고 했던 것 같음 public은 너무 다 보여줌)
구조체란?
→ 여러가지의 데이터를 담을 수 있는 구조를 구상한 것 (틀만 만들어 둔 것)
sql로 치면 테이블(표)을 하나 만들어 둔 거라고 생각하면 된다
✔️ 여러 가지의 데이터를 하나의 키 값에 집어넣기 위해서!
✔️ 배열이나 매핑 형태로 데이터를 만들 수 있다
단점
→ 해당하는 구조체를 전체를 한 번에 리턴하는 방법이 없다 ㅠ (배열은 리턴이 되는데 구조체는 리턴이 불가)
아직은 이게 무슨 말인지 잘 모르겠다... 암튼 그렇다니 그냥 그런줄 알고 머리에 집어넣기...오늘 뇌용량 과부화와서 더 못알아보겠음
deploy시 최초의 한 번만 실행됨 (만들어질 때 최초로 실행되는 함수)
✔️ msg.sender
- 처음 deploy가 될 때 deploy를 하는 사람의 지갑 주소를 뜻한다
→ owner = msg.sender;
- 컨트랙트의 주인 등록!
→ count = 0;
- 초기화!
자바 생성자 생성하면 될 것 같기도?
mapping (string => user_info) internal users;
1. key값은string
타입,usert_info
가 value (구조체와 매핑 데이터 연결한 것임)
2. 가시성 지정자
3.users
는 변수명임
선언 방식: 매핑 데이터의 1. 타입을 지정하고 2.가시성 보여주고 어떠한 3.이름(변수명)으로 쓸 것인지 (이것도 변수임) 선언
데이터의 타입을 먼저 지정해주어야 한다 -
string[]
= '문자열로 이루어진 배열이다'라는 선언
user_info[] public user_list2;
그냥 지금은 방법을 알려주는 것임! 나중에 쓸 때가 오겠죠
얘도 함수임 (무언가 행동을 하겠다는 거임)
modifier
를 나눠놨을까?→ modifier
는 함수의 행동을 변경하는 것(modify라는 말 그대로 '수정')
→ 일반적인 함수, modifier를 만들어주고!
함수를 선언한 다음에 그 뒤에 modifier를 같이 넣어주는 방식!
➡️ add_user()
함수를 선언하고 가시성 지정자 뒤에 위에 만들어둔 onlyOwner
modifier을 넣어준다!
➡️ onlyOwner
얘를 보면 자체 코드 아래에 _;
가 있음. 여기가 add_user()
메소드의 코드가 들어갈 자리라는 뜻임.
➡️
function add_user(
string memory _id, // 키값
string memory _pass,
string memory _name,
uint8 _age
) public onlyOwner {
require(msg.sender == owner, "Only owner can call function");
users[_id].password = _pass;
users[_id].name = _name;
users[_id].age = _age;
}
이렇게 해석하면 됨! 일반 함수에 modifier
를 합쳐준 거임!
// mapping data
// key : value
// mapping (string => user_info)
/*
{
key1 (stirng) : {
password : string,
name : string,
age : uint
}, ....
}
*/
// 구조체[]
/*
[
{
password : string,
name : string,
age : uint
}, ....
]
*/
// json 형태 데이터
/*
data = {
'1': {
'password' : '1234',
'name' : 'test',
'age' : 20
}
}
'age'를 변경하려면?
data['1']['age'] = 30
data = {
'1': 'test1'
}
data에 새로은 '2' 키값과 'test2'라는 value를 추가하려면?
data['2'] = 'test2'
data['1'] = 'tset3'
*/
// 유저의 정보를 출력하는 함수
function view_user(
string memory _id
) public view returns (
string memory,
string memory,
uint8
) {
return (
users[_id].password,
users[_id].name,
users[_id].age
);
}
Remix에서 deploy를 해준다!
그러면 Deployed Contracts
에 컨트랙트가 뜹니다.
public
으로 지정된 변수까지 다 보여준다!
✔️ add_user
에 값을 넣어서 transact 한 다음에 view_user
을 확인해보자
짠! value 값 잘 보여줌
⭐ 없는 아이디 값을 call 하면 안 뜸
사용할 폴더로 이동해서 명령어
npm init -y
로package.json
파일을 만들어준다.
-y
는... 'yes'라는 의미임 ㅋㅋㅋㅋ 네네 다 돼요
그리고 우린 ganache로 개발을 할 거니까
truffle init
도 해줘야 하는데... 먼저npm install truffle -g
로 모듈(?)을 깔아줘야 한다.
응 안돼
^^ 개오래걸림
install 후 다시 명령어를 실행하면 디렉토리 구조가 이렇게 생성되어 있는 것을 알 수 있다!
truffle-config.js
버전을 맞춰줍니다.
성격이 급해서 저기 벌써 컴파일 명령어 적어놨는데... 사실 엔터 치기 전임ㅎ 컴파일 전에 Remix에서 작성해둔 스마트컨트랙트를 contracts
폴더에 생성해준다(test.sol
파일 생성 후 복붙 ㄱㄱ)
그리고 컴파일! 명령어:
truffle compile
이전까지는 Remix로만 컴파일을 했던 거고 드디어 다른거로 컴파일을 해본 것임...!
캡쳐본은 vscode에 solidity 확장자 설치 안해서 색을 잃은 상태임... 확장자 설치해주기! (그냥 익스텐션에 solidity 검색하면 나온다)
deploy를 해보자!
먼저 migrations
폴더에 01_deploy.js
파일 생성
migrations
는 스마트 컨트랙트를 배포 및 관리하는 폴더!
→ 계약을 배포하고 관리하는 일련의 과정을 프로그래밍 언어 중 하나이 JS로 기술한 것
const test = artifacts.require('./Test');
module.exports = function (deployer) {
deployer.deploy(test).then(function () {
console.log(test);
});
};
node.js
✔️ module.exports
: 모듈을 해당 스코프 밖으로 보낼 때 사용
→ module
: 현재 모듈에 대한 정보를 갖고 있는 객체 (예약어), exports
객체를 가지고 있다
→ module.exports
는 하나의 변수나 함수 또는 객체를 직접 할당 (이렇게 할당한 객체 안에 넣어둔 변수나 함수를 메인 파일에서도 사용 가능)
require
: 다른 모듈 사용할 때 사용
스마트 컨트랙트
✔️ artifacts.require();
: 계약 정보(artifact)를 읽어오는 메소드
✔️ deployer.deploy(test)
: 가져온 계약 정보를 배포하는 코드
→ deployer
: truffle이 제공하는 배포를 위한 툴
→ deployer.deploy()
: deployer
이 제공하는 deploy()
함수 호출
➡️ 위에서 읽어온 계약 정보인 test
를 넘겨준다~
참고 링크
https://ko.docs.klaytn.foundation/content/getting-started/quick-start/deploy-a-smart-contract
https://medium.com/dnext-post/solidity-tutorial-6-543bd342d928
deploy 명령어가 따로 있긴 하지만
truffle migrations
,truffle migrate
: compile과 deploy를 동시에! → 가나슈에 배포가 자동으로 됩니다
ganache에 가보면 블록이 생성된 걸 확인할 수 있다! deploy 성공~
build\contracts
폴더 속 Test.json
파일을 확인해보면,
network
에 주소값 생성된 것도 확인할 수 다!
여기까지가 truffle을 이용하여 컴파일을 해본 것입니다! (remix말고 다른 방법을 알게 되었네용~)
📍 개발에 필요한 패키지 설치
➡️npm install express ejs web3
용어 정리
npm
- nodeJS에서 여러 패키지를 설치하고 관리할 때 사용되는 패키지 매니저npm init -y
명령어 실행 → 프로젝트 폴더 내에 package.json
생성되고 npm을 사용해서 패키지를 설치할 때마다 dependency
부분에 설치한 패키지와 버전이 입력된다. express
- 가장 인기 있는 Node 웹 프레임 워크npm install express
→ 프로젝트 폴더에 node_modules
폴더가 생성되고 자동으로 express 패키지가 참조하고 있는 다른 npm 패키지들이 설치된다.ejs
템플릿 엔진 - 템플릿 엔진은 템플릿을 읽어 엔진의 문법과 설정에 따라 파일을 HTML 형식으로 변환시키는 모듈이다!npm install ejs
: EJS 템플릿 설치web3
- JavaScript용 API로 dAPP을 만들기 위한 가장 기본이 되는 API → API를 이용하여 이더리움 네트워크에 접근해서 웹이나 모바일을 개발할 수 있다. (이더리움 노드를 일반 사람들이 알아보기 쉬운 언어로 바꿔서 개발할 수 있게 만들어준다...는 모듈인 것 같다)📍
app.js
작성
→ node module을 로딩하고 초기화해야 하는 변수나 Object를 선언하고 Router에 유입이 이루어지는 유입점 역할을 하는 JavaScrip
모든 파일들의 중심이 되는 파일
// express 로드
const express = require('express');
const app = express();
const port = 3000;
// view 파일들의 기본 경로 설정
app.set('views', __dirname + '/views');
// view engine 설정
app.set('view engine', 'ejs');
app.listen(port, function () {
console.log('server start');
});
➡️ require()
메소드를 통해 외부 모듈을 가져온다. express
모듈을 가져왔다. (http 모듈처럼 사용할 수 있지만 훨씬 더 많은 기능이 있음)
➡️ express 모듈이 가진 주요 메소드
set(name, value)
- 서버 설정을 위한 함수 get(name)
- 설정된 서버 속성을 꺼내온다use([path], function, [function...])
-미들웨어 함수 사용get([path], function)
- 특정 경로로 요청 정보 처리redirect()
- 웹 페이지 경로 강제 이동send()
- 클라이언트에 응답 데이터를 보낸다 ➡️ 미들웨어 - use()
메소드의 매개변수에 입력하는 함수
➡️ listen()
메소드 - http 모듈의 서버 객체가 가진 메소드임... 서버 실행! 그리고 클라이언트를 기다린다.
📍
nodemon app.js
- app.js 서버 오픈 (자동으로 restart)
서버를 오픈했으니 간단하게 회원가입/로그인 기능이 있는 페이지를 만들어 볼 예정!
우선 로그인 화면을 만들어준다!
📍
login.ejs
만들기!
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>로그인</h1>
<form action="/signin" method="post">
<!-- id를 입력하는 공간 -->
<label>id</label>
<!-- 웹데이터는 기본적으로 json! name으로 키 값 지정해줘야 함 (json은 키값, 밸류값 필요하다) -->
<input type="text" name="_id" /> <br />
<label>password</label>
<input type="password" name="_pass" /> <br />
<input type="submit" value="로그인" />
</form>
<!-- 이건 get일까 post일까? 일반적으로 데이터를 요청하는 방식은 get! -->
<a href="/signup">회원 가입</a>
</body>
</html>
간단하게 로그인 기능을 가진 화면을 만들었다. 데이터를 주고받기 위해서는 form 태그 안에 input 태그를 넣어줬다. 키 값과 매치하기 위해서 name 속성 넣어줬다!
로그인을 하기 전에 회원가입을 해야 함! 그래서 a태그로 회원가입 버튼을 만들었다.
근데 /signup
으로 이동할 주소가 없죠?
📍
app.js
에다가signup
,signin
주소 만들어주기
// 회원가입 페이지를 보여주는 주소 생성 (여기서는 화살표 함수를 써봤습니다)
app.get('/signup', (req, res) => {
res.render('signup.ejs');
});
app.post('/signup2', function (req, res) {
// post 형태에서 데이터가 존재하는 곳은? req.body.(key)
const input_id = req.body._id;
const input_pass = req.body._pass;
const input_name = req.body._name;
const input_age = req.body._age;
// 이렇게 데이터가 많으면... 꼭 확인해주기!
console.log(input_id, input_pass, input_name, input_age);
res.send('signup2');
});
➡️ res
객체가 render()
함수를 통해 signup.ejs
파일을 띄워준다. (view engine 설정을 해줬기 때문에 .ejs
는 안써도 되긴 함)
➡️ 입력받은 데이터를 할당해주고 콘솔로 데이터가 잘 들어오는지 확인해준다. 우선은 send()
함수로 화면 잘 띄워주는 건지 확인만 하려고 함!
그리고 회원가입을 위한 창이 없기 때문에 화면을 또 하나 만들어야 한다!
📍
signup.ejs
만들어주기~
→ 솔리디티 파일에서 매핑해준 데이터를 받아와야 함
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>회원가입</h1>
<!-- 유저가 입력한 데이터를 서버에게 보내주기 위해 form 생성-->
<form action="/signup2" method="post">
<label>id</label>
<input type="text" name="_id" /><br />
<label>password</label>
<input type="password" name="_pass" /><br />
<label>name</label>
<input type="text" name="_name" /><br />
<label>age</label>
<input type="number" name="_age" /><br />
<input type="submit" value="회원가입" />
</form>
</body>
</html>
화면이 잘 만들어졌으면 잘 작동하는지 확인을 해봅시다!
회원가입 창으로 넘어가면 아래와같은 화면이 뜬다.
인풋창에 데이터 보내줘 봅시다~
잘 보내줬음! 'signup2' 무사히 뜬당~~
→ 화면 바뀌나 확인하려고 res.send('signup2')
응답 메시지를 띄운 것임
콘솔에도 데이터가 잘 들어왔다.
➡️ require()
함수로 Test.json
파일을 가져온다!
➡️ abi
= Application Binary Interface : 스마트 컨트랙트 안에 존재하는 함수와 매개변수들을 json 형식으로 나타낸 리스트이다. abi
를 사용해 컨트랙트 내의 함수를 호출하거나 컨트랙트로 부터 데이터를 얻을 수 있다.
→ 이런 식으로 json 형식 데이터들이 저장되어 있음
➡️ const contract_address
= Test.json
안의 network
값에서 지갑 주소를 얻어온다.
→ json 파일 구조 잘 확인
➡️ 지갑 주소를 잘 가져오는지 콘솔에 찍어보기
해당 네트워크에서 컨트랙트를 찾아가게 됨
➡️ Web3 = 분산형 인터넷
→ 각 사용자가 노드가 되어 탈중앙화된 분산 네트워크 구성
➡️ Web3
: 이더리움 네트워크와 상호작용 할 수 있는 다양한 메서드를 제공하는 자바스크립트 라이브러리 (블록체인과 상호작용하는 클라이언트 개발)
provider
→ 어떤 네트워크에 연결할지 설정providers
: web3.js
와 연결된 노드 (이더리움 네트워크는 노드로 구성되어 있고, 각 노드는 블록체인의 복사본을 가지고 있다) → provider
클래스를 포함한 객체 리턴 (이 클래스를 사용해 provider
생성 가능setProvider
: provider
가 다른 하위 모듈이 있는 경우 하위 모듈 별로 provider
설정HttpProvider
: http에서 동작하는 node와 연결하여 web3 객체 생성 링크 참고
https://velog.io/@citron03/web3.js%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
https://velog.io/@devjeenie/%EC%84%B8%EC%83%81%EC%9D%84-%EB%B0%94%EA%BF%80-Web-3.0-Web3.js%EB%9E%80
http://www.umlcert.com/ethereum-dapps-14-2/
https://muyu.tistory.com/entry/Ethereum-web3js-%EC%82%AC%EC%9A%A9%EB%B2%95-%EA%B0%84%EB%8B%A8-%EC%9A%94%EC%95%BD
버전 차이 때문에... 안됐음... 고쳐서 쓰세용 (문법이 바뀜)
npm uninstall web3
로 버전 삭제 후
npm install web3@1.10.0
으로 버전 낮춰서 다시... 하면 됨
아니 버전이 달라지면서 문법이 달라지면 인간적으로 동네방네 알려줘야하는거 아니냐
https://docs.web3js.org/guides/web3_upgrade_guide/x/
web3 불러올때
const { Web3 } = require('web3');
해주면 된다
이렇게 스마트 컨트랙트와 연동 끝!
➡️ eth
: 이더리움 블록체인과 컨트랙트와의 상호작용 지원
➡️ Contract()
: 컨트랙트 호출!
💡 Web3.js가 스마트 컨트랙트와 통신하기 위해서는 ABI
와 컨트랙트 주소
가 필요하다
➡️ abi
: 기본적으로 JSON 형태로 컨트랙트의 메소드를 표현하는 것 (어떤 형태로 함수 호출을 해야 컨트랙트가 이해할 수 있는지알려줌)
= 컨트랙트를 배포하고 그 값을 abi코드로 블록체인에 올린다
링크 참고
https://monee1001.tistory.com/37
이더리움 블록체인 web3.js 스마트컨트랙트 실행
세상을 바꿀 Web3.0 - 실습
회원가입을 다시 해보면! 메인페이지로 다시 잘 돌아오고
콘솔에도 잘 찍혀있다.
✔️ 아이디 패스워드가 일치하는 경우 → 로그인 성공
✔️ 로그인 실패
실제 문구는 '아이디 혹은 비밀번호가 틀렸다'는 안내를 함
왜 구체적으로 얘기 안 해줄까?
→ 보안 때문에! (해당 아이디가 존재하는지 존재하지 않는지 알아버리기 때문에)
작성 후 로그인을 시켜보면
res.send(result)
값이 화면에 출력된당~~ 완성~~
✔️ 아이디 틀렸을 때
json 데이터에 빈 값으로 나온다
로그인 성공/실패 코드를 이어서 작성해봅시다!
// 로그인 관련 주소를 생성
app.post('/signin', function (req, res) {
// 유저가 보낸 데이터를 변수에 대입
const input_id = req.body._id;
const input_pass = req.body._pass;
// 값 잘 들어왔는지 확인하는 작업은 꼭 해주세요
console.log(input_id, input_pass);
// smartcontract를 이용하여 해당하는 아이디가 존재하는지 체크
// 데이터가 존재한다면 유저가 입력한 password와 데이터의 password 값을 비교
// 두 값이 같다면 로그인 성공
// 그 외의 경우는 로그인 실패
smartcontract.methods
.view_user(input_id) // 해당 함수가 요구하는 인자는 하나 (키값)! 얘는 데이터를 리턴하기만 해서(호출만 함) 트랜잭션 x 수수료 발생 x
.call() // 호출
.then(function (result) {
// res.send(result); // 데이터가 어떻게 들어오는지 확인해봄
// result는 {'0': password, '1': name, '2': age}
// 로그인이 성공하는 조건
// result['0'] == input_pass 그리고 result['0'] != ""
if ((result['0'] == input_pass) & (result['0'] != '')) {
res.render('index.ejs');
} else {
res.redirect('/');
}
});
});
이런 에러가 나오는게 정상이다! index.ejs
를 찾을 수 없기 때문이다... 왜냐면 우린 아직 ejs를 만들지 않았음 ㅋㅋㅋㅋ
index.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
function user_list() {
location.href = '/user_list';
}
</script>
</head>
<body>
<h1>Index Page</h1>
<p>(<%=name%>)님 환영합니다.</p>
<button onclick="user_list()">회원 리스트 보기</button>
</body>
</html>
화면에 로그인 한 사람의 이름을 띄우고 싶어용
→ app.js
수정하러 가봅시다
✔️ 함수 기본 원리
// 매개변수가 존재하는 기본값 지정 함수
function func_3(a, b = 3) {
result = a + b;
return result;
}
console.log(func_3(3));
// 기본값을 바꾸고 싶다면?
console.log(func_3(3, 5)); // 걍 바꿔줌 응 나 5 넣을거야~ 하는 느낌 (값을 안 집어넣었을 때는 기본값을 주는거고, 값을 집어넣으면 저항없이 바꿔줌)
이런 방식으로
json 값을 , 뒤에 넣어주고 이걸 html위에 띄우기 위해 다시 index.ejs
로 고고
태그 안에 %가 있으면 ejs가 '아하 이건 javascript구나!'라고 인지한 후 변수의 값을 이 태그 안에 할당시켜준다
→ 만약 =
를 안 쓰면 그냥 자바스크립트 코드를 쓸 수 있음
로그인 성공입니다~!!
modifier 추가, 배열 데이터 push()
메소드 추가
→ 유저가 얼마나 가입했는지 확인하려는 거임
유저 수 세는 함수 추가
배열 출력할 수 있는 함수 추가
→ 모든 유저의 정보를 가져올 수 있다
truffle migrate
test1, test2 계정 생성
index.ejs
에 버튼 태그 추가
주소 만들었으니까... app.js
로 가야겠죵
➡️ async
, await
는 따로 글 써서 공부해보자!
막히는 부분 없이 실행시키고 실행시킬 때 쓴 다는 것 같다...?
그리고 user_list.ejs
를 만들어준다!
그럼 로그인 후 버튼 눌렀을 때
이렇게 나오면 일단 성공~
일단 이렇게 나옴... 시간부족으로 우선 여기서 끝