[TIL] 2023-05-15 DAY40
GIT 주소 : https://github.com/dowoo303/SOLIDITY
오늘 배운것들
- TEST2 풀이
1) mapping 활용
2) require- Constructor
1) MODIFIER- TEST3
각종 팁
- uint가 bytes형태로 변환 못하는 이유: uint는 정적이고 bytes는 동적이기 때문이다.
- payable: 돈을 받는 쪽
- mutex개념: modifier을 여러개 겹칠경우 순서를 잘 생각해봐야한다. 대략적인 틀을 예로 들면
ex) 원함수 m1 m2 인 경우 대충 (m1 m2 원 m2 m1) 이런 순서인데 modifier의_위치에 따라 저 5개가 다 있는 것이 아니라 어느정도 차이가 있을 수 있다고 생각하면 된다.
ex) (m1 원 m2 m1) (원 원 원 m2 m1) 등등등 -> 단, m1과 m2 원 함수의 위치는 바뀔 수 없다.(틀은 무조건 유지) 그리고 modifier는 여러번 사용할 수 없지만 원함수는_의 갯수에 따라 여러번 사용이 가능해진다.- // msg.sender: 거래를 일으키는 사람
// payable: 해당 스마트 컨트랙트에 돈을 보내고 싶음 = 컨트랙트는 돈을 받고 싶음
회고
- 테스트 시간이 짧은데는 이유가 있다 -> 코드가 간단할 확률이 높음(return 한줄로 끝날 확률 높음)
- 이제 슬슬 어려워진다. -> 복습 필요
require
형태: require(조건, "오류 메세지");
-> 헷갈리지 말아야할 건 조건을 만족 못해야 뒤 오류 메세지를 띄운다는 사실!
if와 다르게 조건을 만족시키지 않으면 그 함수 안의 코드들이 실행되지 않는다.(조건을 만족하지 않으면 require에서 끊어버리기 때문에 가스비를 아낄 수 있다.)
-> for문이나 if문을 쓸 것 없이 require을 쓰면 코드를 간단히 구성할 수 있다.
-> require의 위치에 상관없이 조건 만족을 하지 않는 순간 함수 자체가 실행된적 없는 것처럼 동작한다.(reverted to the initial state)
mapping(string => bytes32) ID_PW에서 struct를 추가한 mapping(string => User) ID_PW으로 변경해줘야한다.contract TEST2_Answer {
struct User {
bytes32 hash;
uint attempts;
}
mapping(string => User) ID_PW; // db라고 생각하면 됨
// 정리를 위하여 간단한 함수 하나 생성(아이디와 비밀번호 hash 생성함수)
function getHash(string memory _ID, string memory _PW) public pure returns(bytes32) {
return keccak256(abi.encodePacked(_ID, _PW));
}
// * 로그인 기능 - ID, PW를 넣으면 로그인 여부를 알려주는 기능
// id와 비밀번호가 쌍으로 관리
function logIn(string memory _ID, string memory _PW) public returns(bool) {
// * 비밀번호 5회 이상 오류시 경고 메세지 기능 - 비밀번호 시도 회수가 5회되면 경고 메세지 반환
require(ID_PW[_ID].attempts < 5, "Too much attempts");
if(ID_PW[_ID].hash == getHash(_ID, _PW)) { // 2개 이상 넣을 때는 bytes로는 힘들기 때문에 abi.encodePacked() 사용한다.
ID_PW[_ID].attempts = 0;
return true;
} else {
ID_PW[_ID].attempts++;
return false;
}
}
// * 회원가입 기능 - 새롭게 회원가입할 수 있는 기능
function signIn(string memory _ID, string memory _PW) public {
// * 회원가입시 이미 존재한 아이디 체크 여부 기능 - 이미 있는 아이디라면 회원가입 중지
require(ID_PW[_ID].hash == 0x0000000000000000000000000000000000000000000000000000000000000000, "Provided ID is already being used.");
ID_PW[_ID].hash = getHash(_ID, _PW); // 2개 이상 넣을 때는 bytes로는 힘들기 때문에 abi.encodePacked() 사용한다.
}
// * 회원탈퇴 기능 - 회원이 자신의 ID와 PW를 넣고 회원탈퇴 기능을 실행하면 관련 정보 삭제
function signOut(string memory _ID, string memory _PW) public {
// require(logIn(_ID, _PW) == true);
require(ID_PW[_ID].hash == getHash(_ID, _PW));
delete ID_PW[_ID].hash;
}
}
contract REQUIRE {
function Require1(uint _n) public pure returns(uint) {
require(_n<10, "input should be lower than 10");
return _n*3;
}
function getName(string memory _name) public pure returns(bytes32) {
return keccak256(abi.encodePacked((_name)));
}
// getName(alice): 0x9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501
function onlyAlice(string memory _name) public pure returns(bool) {
require(getName(_name) == 0x9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501);
// 무조건 결과값이 1개로 고정된다(true만 나옴) - 어짜피 위 require에서 다 끊어버리기 때문
return true;
}
}
contract REQUIRE2 {
function getBool() public pure returns(bool) {
bool _a;
return _a;
}
function Require1() public pure returns(uint) {
uint _a=1;
bool b; // 디폴트 false
require(b, "Error"); // 통과못함
return _a;
}
function Require2() public pure returns(uint) {
uint _a=1;
bool _b; // 디폴트 false
return _a;
require(_b, "Error"); // Unreacheable code
}
// require의 동작 더 자세히 알아보기 -> require가 가장 밑에 있어도 위에 실행한 코드까지 모두 무효화 시켜버린다.(마치 함수를 실행한적 없는것 처럼)
uint a = 1;
function getA() public view returns(uint) {
return a;
}
function Require3() public {
bool c;
a = 5;
require(c, "error"); // 위에서 a를 5로 바꾼 것도 전부 다 revert(원래 상태로 복구) 시킨다.
}
// 밖에 있는 다른 함수를 실행시키면 require의 동작은? -> 이것도 모두 무효화
function setAasFive() public {
a = 5;
}
function Require4() public {
bool c;
setAasFive();
require(c, "error");
}
// require에 조건 여러개 넣기(and, or 다 가능)
function Require5(uint _n) public pure returns(bool) {
require(_n%5==0 && _n>10, "Nope");
return true;
}
// if문 안의 require
function Require6(uint _a) public pure returns(uint) {
if(_a%3==0) {
require(_a%3!=0, "nope");
} else if(_a%3 == 1) {
return _a%3;
} else {
return _a%3;
}
}
}
ToDo
- constructor 기초
- payable과 결합해서 사용해보기(CONSTRUCTOR5)
- MODIFIER
관련 팁들
- 하나의 함수를 만드는 것이라 생각하면 된다. -> 단, 배포와 함께 실행된다.
- 시작할때부터 어떤 값을 설정하고 시작할 때 사용한다.(특히 변화하면 안되는 값들을 설정하고 들어갈때 많이 사용) -> 예를 들면 오너를 설정할 때 많이 사용한다.

-> constructor에 input값을 넣으면 deploy옆에 값을 설정할 수 있는 창이 생긴다.

-> msg.value가 가르키는 value
MODIFIER
형태: 일반 함수와 비슷하다. 단, 마지막에
_;가 붙는다._의 의미는 함수의 실행시점을 말하며, modifier함수를 호출한 함수의 코드들이_에서 실행된다는 뜻이다.
장점
1. 반복적인 작업을 줄여준다.
2._을 여러개 사용하여 일반 함수를 반복시킬 수 있다.
3. 일반함수를 실행시키기 전, 후로 코드를 짤 수 있다. -> 활용도가 높음
활용법: 일반함수 public 옆에 modifier함수 명을 적어서 사용한다. 그러면 modifier함수에서 설정한 실행시점에서 일반함수가 시작된다. -> 이를 활용하여 mutex 사용가능
// 생성자+payable
contract CONSTRUCTOR5 {
/*
1. 1번 지갑으로 배포, value는 10eth로
2. 배포 후 지갑 잔고 확인
3. 2번 지갑으로 deposit() 1eth // 3,4,5번 지갑으로 똑같이 실행
4. 지갑 잔고 확인 후, 2번 지갑으로 transferTo 시도, _to의 지갑 주소는 6번 지갑 금액은 5eth
5. 1번 지갑으로 transferTo 시도, _to의 지갑 주소는 6번 지갑 금액은 5eth
6. 2번 지갑으로 withdraw 함수 시도, 1번 지갑으로 withdraw 함수 시도
0x17F6AD8Ef982297579C203069C1DbfFE4348c372
*/
// 트랜잭션: from은 내 주소고, to는 컨트랙트 주소
// msg.sender: 거래를 일으키는 사람
address payable owner;
// 해당 컨트랙트에 돈을 보내고 싶음 = 컨트랙트는 돈을 받고 싶음 -> payable
constructor() payable {
payable(this).transfer(msg.value); // 돈을받고싶음(이 컨트랙트주소로).돈을보낸다(value만큼)
// 이 컨트랙트를 배포한 사람이 오너다
owner = payable(msg.sender); // 여기서 msg.sender는 컨트랙트 배포자를 말하며, owner에 이 컨트랙트 배포자를 넣어 고정값으로 만들어둔다.
}
function getOwner() public view returns(address) {
return owner;
}
// 받는 사람이 컨트랙트면 실행되는 함수 - 이게 있어야 위 코드가 작동함(컨트랙트가 돈을 받을 수 있음)
receive() external payable{} // 일반거래(별도의 호출되는 함수가 없는 거래)시 해당 contract가 돈을 받을 수 있게 해주는 함수
// 특정 지갑주소에 특정 금액만큼 보내는 함수
// 컨트랙트로부터 돈을 받는 함수
function transferTo(address payable _to, uint _amount) public {
require(msg.sender == owner, "only owner can transfer asset"); // 이 지갑의 오너만이 이 컨트랙트의 자산을 이동(보낼수)시킬 수 있다
_to.transfer(_amount);
}
// 특정 금액 만큼 돈을 받을 수 있는 함수
// 특정 금액을 컨트랙트에 돈을 넣는 함수
function deposit() public payable returns(uint) {
return msg.value;
}
// 컨트랙트가 owner에게 잔고 전부를 돈을 보내는 함수 - 오너 입장에서는 전액 인출
function withdraw1() public {
require(msg.sender == owner, "only owner can transfer asset");
owner.transfer(address(this).balance); // 이 컨트랙트 잔고를 모두 오너에게 보내라.
}
// 컨트랙트가 owner에게 _amount만큼 보내는 함수
function withdraw2(uint _amount) public {
require(msg.sender == owner, "only owner can transfer asset");
// owner.transfer(address(this).balance); // 이 컨트랙트 잔고를 모두 오너에게 보내라.
owner.transfer(_amount /* _amount *10**18 */);
}
// 컨트랙트가 owner에게 1이더 보내는 함수
function withdraw3() public {
require(msg.sender == owner, "only owner can transfer asset");
owner.transfer(1 ether); // 이 컨트랙트 잔고를 모두 오너에게 보내라.
}
}
// modifier 실제 응용2(화장실 사용 체크)
contract MODIFIER5 {
uint mutex=0;
modifier m_check {
mutex++;
_;
mutex--;
}
modifier shouldBeZero {
require(mutex==0, "someone is using");
_;
}
modifier shouldBeOne {
require(mutex==1, "exist one");
_;
}
function inAndOut() public m_check returns(string memory) {
return "Done";
}
// 실행안됨
function inAndOut2() public m_check shouldBeZero returns(string memory) {
return "Done";
}
// 위와 다르게 실행 됨
function inAndOut2_2() public m_check shouldBeOne returns(string memory) {
return "Done";
}
// 방 빈것 확인하고 inAndOut Done하기
function inAndOut3() public shouldBeZero m_check returns(string memory) {
return "Done";
}
function occupy() shouldBeZero public {
mutex++;
}
function vacancy() public {
mutex--;
}
}