[TIL] Part2 - DAY40 Solidity - require, constructor, modifier

박동우·2023년 5월 15일

블록체인 스쿨 3기

목록 보기
38/72

[TIL] 2023-05-15 DAY40


Solidity - require, constructor, modifier

GIT 주소 : https://github.com/dowoo303/SOLIDITY


오늘 배운것들

  1. TEST2 풀이
    1) mapping 활용
    2) require
  2. Constructor
    1) MODIFIER
  3. TEST3

각종 팁

  1. uint가 bytes형태로 변환 못하는 이유: uint는 정적이고 bytes는 동적이기 때문이다.
  2. payable: 돈을 받는 쪽
  3. mutex개념: modifier을 여러개 겹칠경우 순서를 잘 생각해봐야한다. 대략적인 틀을 예로 들면
    ex) 원함수 m1 m2 인 경우 대충 (m1 m2 원 m2 m1) 이런 순서인데 modifier의 _ 위치에 따라 저 5개가 다 있는 것이 아니라 어느정도 차이가 있을 수 있다고 생각하면 된다.
    ex) (m1 원 m2 m1) (원 원 원 m2 m1) 등등등 -> 단, m1과 m2 원 함수의 위치는 바뀔 수 없다.(틀은 무조건 유지) 그리고 modifier는 여러번 사용할 수 없지만 원함수는 _의 갯수에 따라 여러번 사용이 가능해진다.
  4. // msg.sender: 거래를 일으키는 사람
    // payable: 해당 스마트 컨트랙트에 돈을 보내고 싶음 = 컨트랙트는 돈을 받고 싶음

회고

  1. 테스트 시간이 짧은데는 이유가 있다 -> 코드가 간단할 확률이 높음(return 한줄로 끝날 확률 높음)
  2. 이제 슬슬 어려워진다. -> 복습 필요



💻 Solidity 코딩

✅ TEST2 풀이

require

형태: require(조건, "오류 메세지");
-> 헷갈리지 말아야할 건 조건을 만족 못해야 뒤 오류 메세지를 띄운다는 사실!
if와 다르게 조건을 만족시키지 않으면 그 함수 안의 코드들이 실행되지 않는다.(조건을 만족하지 않으면 require에서 끊어버리기 때문에 가스비를 아낄 수 있다.)
-> for문이나 if문을 쓸 것 없이 require을 쓰면 코드를 간단히 구성할 수 있다.
-> require의 위치에 상관없이 조건 만족을 하지 않는 순간 함수 자체가 실행된적 없는 것처럼 동작한다.(reverted to the initial state)

  • 비밀번호 5회 이상 오류 문제를 풀 때는 오류 횟수 체크가 필요하기 때문에 struct가 추가되어야한다. 그래서 기존에 쓰던 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;
        }
    }

}



✅ Constructor(생성자)

ToDo

  1. constructor 기초
  2. payable과 결합해서 사용해보기(CONSTRUCTOR5)
  3. MODIFIER

관련 팁들

  1. 하나의 함수를 만드는 것이라 생각하면 된다. -> 단, 배포와 함께 실행된다.
  2. 시작할때부터 어떤 값을 설정하고 시작할 때 사용한다.(특히 변화하면 안되는 값들을 설정하고 들어갈때 많이 사용) -> 예를 들면 오너를 설정할 때 많이 사용한다.


-> 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--;
    }


}
profile
HELLO!

0개의 댓글