[Web3] Solidity 문법 기초 4

Gunter·2024년 7월 25일
0

Web3

목록 보기
5/5

수정자 modifier

  • 확인해야 할 접근 통제 규칙을 명시하고 신뢰와 프라이버시를 구축하기 위해 누가 데이터와 함수에 대한 통제권을 갖는지 관리
  • 가시성 수정자와 구분해서 데이터와 함수의 액세스 수정자(access modifier)라고 부르기도 함

 

// 파라미터 값이 없는 경우   
   modifier 모디파이어명{
         revert 나 require
         _;
    }
 
 // 파라미터 값이 있는 경우
    modifier 모디파이어명(파라미터){
          revert 나 require
         _;
    }

 

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract lec27{

    modifier onlyAdults2(uint256 _age){
         require(_age>18,"You are not allowed to pay for the cigarette");
         _;
    }

    function BuyCigarette2(uint256 _age) public onlyAdults2(_age) returns(string memory){
        return "Your payment is scceeded";
    }
  
}

여기서는 modifier인 onlyAdults2는 uint256 타입인 _age 파라미터를 받음.
이 _age를 받고 18세 미만이라면 require를 통해서 에러를 발생시킴.

즉, Buycigarette2 _age 라는 파라미터 값을 onlyAdults2 라는 모디파이어에 _age 넣어준 것.

그러면, Buycigarette2(uint256 _age) 는 실질적으로 이렇게 작동하게 됨.

function BuyCigarette2(uint256 _age) public returns(string memory){
        require(_age>18,"You are not allowed to pay for the cigarette");
        return "Your payment is scceeded";
    }

 


payable

Payable은 코인과 상호작용(송금)시 필요한 키워드.

즉, send, trnafer, call을 이용하여, 이더를 보낼때 Payable이라는 키워드가 필요.

이 Payable은 주로 함수,주소,생성자에 붙여서 사용됨.

// SPDX-Lisence-Identifier: GPL-3.0
pragma solidity ^0.8.13;
import "hardhat/console.sol";
contract Counter {
    address payable public owner;
    constructor() payable {
        owner = payable(msg.sender);
    }
    function deposit() public payable {

    }
    function NotpayableDepisit() public {

    }
    function withdraw() public payable {
        uint amount = address(this).balance;
        console.log(amount / 1 ether,"ETH");
        (bool success, ) = owner.call{value: amount}(""); 
        require(success, "Failed to send Other.");
    }
    function transfer(address payable _to, uint _amount) public {
        (bool success,) = _to.call{value: _amount*(1 ether)}("");
        require(success, "Failed to send Other.");
    }
}

msg.value

msg.value는 송금보낸 코인의 값. 호출자의 Address를 가져옴

ether를 보내는 함수 send, transfer, recall

1.send : 2300 gas를 소비, 성공여부를 true 또는 false로 리턴한다
2.transfer : 2300 gas를 소비, 실패시 에러를 발생
3.call : 가변적인 gas 소비 (gas값 지정 가능), 성공여부를 true 또는 false로 리턴. 재진입(reentrancy) 공격 위험성 있음, 2019년 12월 이후 call 사용을 추천.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.5.0 < 0.9.0;

contract lec32 {


   event howMuch(uint256 _value);
   
   function sendNow(address payable _to) public payable{
       bool sent = _to.send(msg.value); // return true or false
       require(sent,"Failed to send either");
       emit howMuch(msg.value);
   }
   
   function transferNow(address payable _to) public payable{
       _to.transfer(msg.value);
       emit howMuch(msg.value);
   }
   
   function callNow (address payable _to) public payable{
       // ~ 0.7
       (bool sent, ) = _to.call.gas(1000).value(msg.value)("");
       require(sent,"Failed to send either");
       
       //0.7 ~
       // (bool sent, ) = _to.call{value: msg.value , gas:1000}("");
       // require(sent, "Failed to send Ether");
       emit howMuch(msg.value);
       
   }
   
}

 

  • Ether을 받는 방법
    • Receive() external payable
    • Fallback() external payable
    • Receive()의 경우는 msg.data, 즉 calldata가 비어있는 경우 사용되고, 다른 경우에는 fallback() 함수를 호출하면 됨

 



balance, msg.sender

balance
balance는 해당 특정 주소의 현재 이더의 잔액을 나타 냅니다.
주소.balance 와 같은 형태로 사용합니다.

msg.sender
msg.sender 는 스마트컨트랙과 상호 작용하는 주체.


pragma solidity >=0.7.0 < 0.9.0;


contract MobileBanking{
    
 
    event SendInfo(address _msgSender, uint256 _currentValue);
    event MyCurrentValue(address _msgSender, uint256 _value);
    event CurrentValueOfSomeone(address _msgSender, address _to,uint256 _value);
   
    function sendEther(address payable _to) public payable {
        require(msg.sender.balance>=msg.value, "Your balance is not enough");
        _to.transfer(msg.value);    
        emit SendInfo(msg.sender,(msg.sender).balance);
    }
    
    function checkValueNow() public{
        emit MyCurrentValue(msg.sender, msg.sender.balance);
    }
    
    function checkUserMoney(address _to) public{
        emit CurrentValueOfSomeone(msg.sender,_to ,_to.balance);
    }
    
}



 

msg.sender는 스마트 컨트랙과 상호작용하는 주체를 나타내기에, 이점을 이용해서

require관 연계하여 특정 주소를 가진 주체에게만 특정 함수를 사용할 수 있도록 권한을 부여 할 수 있다.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 < 0.9.0;

contract MobileBanking{
    
    address owner;
    constructor() payable{
        owner = msg.sender;
    }
    
    modifier onlyOwner{
        require(msg.sender == owner, "Only Owner!");
        _;
    }
    
    event SendInfo(address _msgSender, uint256 _currentValue);
    event MyCurrentValue(address _msgSender, uint256 _value);
    event CurrentValueOfSomeone(address _msgSender, address _to,uint256 _value);
   
    function sendEther(address payable _to) public onlyOwner payable {
       
        require(msg.sender.balance>=msg.value, "Your balance is not enough");
        _to.transfer(msg.value);    
        emit SendInfo(msg.sender,(msg.sender).balance);
    }
    
    function checkValueNow() public onlyOwner {
        emit MyCurrentValue(msg.sender, msg.sender.balance);
    }
    
    function checkUserMoney(address _to) public onlyOwner {
        emit CurrentValueOfSomeone(msg.sender,_to ,_to.balance);
    }
    
}

 



fallback, receive

fallback
Fallback은 함수지만, 어떠한 전달 인자, return 값도 가지지 않음

특징
1. 먼저 무기명 함수, 이름이 없는 함수입니다.
2. external 필수
3. payable 필수
4. 존재하지 않는 함수를 호출할 때 FallBack이 자동으로 호출됨
5. Ether가 Contract로 바로 Send 될 때 Call 됨

(receive, msg.data가 없는 경우)
  1. FallBack Transfer, send가 호출될 때 2300gas 제한 있음

 

receive: 순수하게 이더만 받을때 작동 합니다.
fallback: 함수를 실행하면서 이더를 보낼때, 불려진 함수가 없을 때 작동합니다.

 

기본형 : 불려진 함수가 특정 스마트 컨트랙이 없을때 fallback 함수가 발동 합니다.
fallback() external {

}

payable 적용시 : 이더를 받고 나서도 fallaback 함수가 발동합니다.
fallback() external payable {

}

receive() external payable{

}

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.5.0 < 0.9.0;


contract Bank{
    event JustFallback(address _from,string message);
    event RecevieFallback(address _from,uint256 _value ,string message);
    event JustFallbackWIthFunds(address _from,uint256 _value ,string message);
    //~0.6 
//   function() external payable {
//      emit JustFallbackWIthFunds(msg.sender, msg.value,"JustFallback is called");
//     }
    
    
    //0.6~
    // fallback() external {
    //   emit JustFallback(msg.sender,"JustFallback is called");
    // }
    // receive() external payable {
    //   emit RecevieFallback(msg.sender, msg.value,"RecevieFallback is called");
    // }
    
    //
    fallback() external payable {
     emit JustFallbackWIthFunds(msg.sender, msg.value,"JustFallbackWIthFunds is called");
    }
  
}

contract You{

    //receve() 
    function DepositWithSend(address payable _to) public payable{
         bool success = _to.send(msg.value);
         require(success, "Failled" );
    }
    
    function DepositWithTransfer(address payable _to) public payable{
        _to.transfer(msg.value);
    }
    
    function DepositWithCall(address payable _to) public payable{
        // ~ 0.7
        // (bool sent, ) = _to.call.value(msg.value)("");
        // require(sent,"Failed to send either");
        
        //0.7 ~
        (bool sent, ) = _to.call{value: msg.value}("");
        require(sent, "Failled" );
    }
    
    //fallback()
    function JustGiveMessage(address payable _to) public payable{
        (bool success, ) = _to.call("HI");
        require(success, "Failled" );
    }
    
    //To the fallback() with Funds
    function JustGiveMessageWithFunds(address payable _to) public payable{
        (bool success,) = _to.call{value:msg.value}("HI");
        require(success, "Failled" );
    }
    
}

 



call

  • Call은 다른 컨트랙트와 상호작용하는 저수준(low level) 함수
  • Fallback 함수 호출을 통해서 Ether를 보낼경우 이 방법 추천
  • 존재하는 함수를 호출하는 방법으로는 적절치 않음
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 < 0.9.0;

contract add{
    event JustFallback(string _str);
    function addNumber(uint256 _num1, uint256 _num2) public pure returns(uint256){
        return _num1 + _num2;
    }
    fallback() external {
     emit JustFallback("JustFallback is called");
    }
}

contract caller{
    event calledFunction(bool _success, bytes _output);
   
    //1. 송금하기 
    function transferEther(address payable _to) public payable{
        (bool success,) = _to.call{value:msg.value}("");
        require(success,"failed to transfer ether");
    }
    
    //2. 외부 스마트 컨트랙 함수 부르기 
    function callMethod(address _contractAddr,uint256 _num1, uint256 _num2) public{
        (bool success, bytes memory outputFromCalledFunction) = _contractAddr.call(
              abi.encodeWithSignature("addNumber2(uint256,uint256)",_num1,_num2)
              );
        require(success,"failed to transfer ether");
        emit calledFunction(success,outputFromCalledFunction);
    }
}

 



delegate call

  • 호출된 Contract의 코드가 호출한 Contract의 context에서 수행됨
  • Msg.sender 와 msg.value가 변하지 않음
  • Contract가 Runtime에 서로 다른 주소의 Contract를 호출할 수 있음
  • 스토리지, 주소, 잔액은 여전히 호출 Contract 참조
  • 코드만 호출 주소에서 가져옴
  • 업그레이드 가능한 Smart Contract의 기본개념

https://dayone.tistory.com/38

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 < 0.9.0;


contract add{
    uint256 public num = 0;
    event Info(address _addr,uint256 _num);
    
    function plusOne() public {
        num = num + 1;
        emit Info(msg.sender,num);
}
    
}

contract caller{
    uint256 public num = 0;
    function callNow(address _contractAddr) public payable{
        (bool success,) = _contractAddr.call(abi.encodeWithSignature("plusOne()"));
        require(success,"failed to transfer ether");
    }
    function delcateCallNow(address _contractAddr) public payable{
        (bool success,) = _contractAddr.delegatecall(abi.encodeWithSignature("plusOne()"));
        require(success,"failed to transfer ether");
    }
}
 

 



enum

enum 은사람이 읽을수 있게, 사용자/개발자에 의해 정의된 상수세트 타입.

한개의 enum당 256개까지 저장이 되며, 0 부터 255까지 부여됨.
(uint8 = 0~255(2^8-1))
상수세트이기에 enum은 uint로 변환해서 사용이 가능

// SPDX-Lisence-Identifier: GPL 3.0
pragma solidity ^0.8.13;
contract Example {
    enum Status{
        Pending,
        Shipped,
        Accepted,
        Rejected,
        Canceled
    }
    Status public status;
    function get() public view returns(Status) {
        return status;
    }
    function set(Status _status) public {
            status = _status;
    }
    function cancel() public {
        status = Status.Canceled;
    }
    function reset() public{
        delete status;
    }
}
  • enum 이름 {} 으로 선언 가능
  • delete를 통해 삭제 가능
  • Canceld를 통해 상태 취소 가능

 



library

library: 기존에 만들던 스마트 컨트랙과 다른 종류의 스마트 컨트랙

이점
1. 재사용 : 블록체인에 라이브러리가 배포되면, 다른 스마트 컨트랙들에 적용가능.
2. 가스 소비 줄임 : 라이브러리는 재사용가능 한 코드, 즉 여러개의 스마트 컨트랙에서 공통으로 쓰이는 코드를 따로 라이브러리 통해서 배포 하기에, 다른 스마트 컨트랙에 명시를 해주는것이 아니라, 라이브러리를 적용만 하면 되기에 가스 소비량을 줄일 수 있다. 왜냐하면, 가스는 스마트 컨트랙의 사이즈/길이에 영향을 많이 받기 때문이다.
3.데이터 타입 적용: 라이브러리의 기능들은 데이터 타입에 적용할 수 있기에, 좀 더 쉽게 사용할 수 있다.

제한사항
1. fallback 함수 불가: fallback 함수를 라이브러리 안에 정의를 못 하기에, 이더를 갖고 있을 수 없습니다.
2. 상속 불가
3. payable 함수 정의 불가

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 < 0.9.0;

library SafeMath{
    function add(uint8 a, uint8 b) internal pure returns (uint8) {
        require(a+b >= a , "SafeMath: addition overflow");
        return a + b;
    }
}

contract lec40{
    using SafeMath for uint8;
    uint8 public a; 
    
    function becomeOverflow(uint8 _num1,uint8 _num2) public  {
       // a = _num1.add(_num2);
        a = SafeMath.add(_num1 ,_num2);
       
    } 
}

 



import

솔리디티 파일 한개당 스마트 컨트랙트를 한개씩 넣는다면, 여러개의 솔리디티 파일이 생길것이다.
그렇다면, 스마트 컨트랙끼리 이어 줄려면, 서로의 솔리디티 파일의 위치를 잘 알아야한다.
이럴때 필요한것이 import 입니다.
스마트 컨트랙트든, library 든 import 를 통해서 위치를 명시해주ㅜㄴ다.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.6.0 <0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/docs-v3.x/contracts/math/SafeMath.sol";

contract lec41 {
    using SafeMath for uint256;
    uint public a;
    uint public maximum = ~uint256(0); // ==2**256-1; // 2**256 == 2^256
    function becomeOverflow(uint _num1, uint _num2) public {
        a = _num1.add(_num2);
    }
}

0개의 댓글