Solidity

장민우·2021년 12월 20일
1

Solidity

목록 보기
4/6
post-thumbnail

Solidity

1. 버전 설정

pragma solidity ^0.4.19; // 0.4.19 버전을 사용한다는 의미
pragma solidity >= 0.7.0 < 0.9.0; // 0.7.0~0.9.0 사이의 버전을 사용한다는 의미
Solidity는 버전이 중요하다. 사용할 때 항상 버전을 명시를 해줘야 한다.

2. 컨트랙트

pragma solidity >= 0.7.0 < 0.9.0;

contract Test{

}
Java로 따지면 클래스라고 생각하면 된다. 모든 함수나 코드의 실행은 이 contract안에
서 만 실행되어야 한다.

3.자료형

  1. uint(부호가 없는 정수형)
    uint는 기본적으로 256비트 자료형으로 , uint8, uint32, uint64 ... 등으로 사용가능
  2. int(정수형)
    부호를 포함한 정수형으로 uint보다는 양수를 사용하기에는 부족하다.
  3. bool(논리형)
    Java에서 마찬가지 boolean형과 똑같다 값은 true,false 밖에 못 들어간다.
  4. string(문자형)
    UTF-8 인코딩 된 문자열 함수에서 사용할려면 memory를 꼭 붙어야 한다.
  5. bytes(바이트)
    말 그대로 바이트형 값을 담는 변수이다.
  6. address(주소)
    스마트 컨트랙을 이용할 이더리움의 주소 값을 뜻 한다.

4. 구조체

pragma solidity >- 0.7.0 < 0.9.0;
contract Test{
uint256 public a = 16;
uint256 private b = 10;

    struct test{
        string public name;
        uint256 public age;
    }
}
하나의 VO객체를 선언하는 것과 비슷하다. test라는 이름으로 각각의 들어갈 변수들을 설정해서 test라는 이름으로 묶어서 사용하는 것 이다.
(public private 는 접근제한자 이다.)

5. 배열

pragma solidity >- 0.7.0 < 0.9.0;
contract Test{
	string[] name public;
	string[3] name2 public;
	string[] name3 public = ["마루치","아라치","아구몬"];

    
}
배열또한 선언이 가능하다 맨 위에 name이라는 배열은 자료형이 string이고 길이를 지정하지 않은 ArrayList와 같은 가변적 길이를 가진 배열이다.
name2는 자바의 배열처럼 고정적 길이를 가진 배열을 선언 한 것이다.
name3는 배열을 선언함과 동시에 안에 있는 값들을 넣어주는 방식이다.
(솔리디티에는 정적 배열과 동적 배열이 존재합니다.

정적 배열 : uint[4] fixedArray 처럼 미리 사용할 배열의 크기를 지정함.

동적 배열 : uint[] dynamicArray 처럼 고정된 크기가 없어 계속 커질 수 있음.

new 키워드를 활용한 배열 : new uint[](5) 처럼 길이 인자를 반드시 명시해줘야 함.)

6.Visibility

1. external
public 처럼 모든 곳에서 접근 하는가, exteranal 이 정의된 자기자신의 스마트컨트랙 접근 불가
2. public
모든 곳 에서 접근이 가능하다는 얘기이다.
3. private
오직 private이 정의된 자기자신의 스마트컨트랙에서만 가능(ptivate이 정의된 현재 이 컨트랙을 상속 받은 자식 컨트랙도 접근이 불가하다.)
4. internal
private 처럼 오직 internal이 정의된 자기자신의 스마트컨트랙에서만 가능(internal이 정의된 현재 이 컨트랙을 상속받은 자식 컨트랙도 접근이 가능하다.)

7. function 설정

contract lec6{
     uint256 public a = 1;
    
    function viewExample() public view returns(uint256){
        return a+2;
    }
    
    function readAndChangeA() public returns(uint256){
        a = 3;
        return a+2;
    } 
    
    function pureExample() pure public returns(uint256){
        uint256 a2 = 3;
        return a2+2;
    } 
}
  1. view : function 밖의 변수들을 읽을 수 있으나 변경 불가능
  2. pure : function 밖의 변수들을 읽지 못하고, 변경도 불가능
  3. view와 pure 명시 안할 때 : function 밖의 변수들을 읽어서, 변경을 해야한다.
    정의된 현재 컨트랙을 상속받은 자식 컨트랙도 접근 가능

8. Solidity storage와 memory

  1. storage : 대부분의 변수, 함수들이 저장되며, 영구적으로 저장이 된다.(단 가스비용이 많이 든다.)
  2. memory : 함수의 파라미터, 리턴값, 래퍼런스 타입이 저장되는 공간
    (단 storage 처럼 영구적이지 않고 함수내에서만 사용되기 때문에 storage 보다 가스비용이 저렴하다.)
  3. Colldata : 주로 external function 의 파라미터에서 사용됩니다.
  4. stack : EVM(Ethereum Virtual Machine) 에서 stack data를 관리할때 쓰는 영억인데 1024MB 제한적이다. 이 컨트랙을 상속받은 자식 컨트랙또한 사용이 가능하다.

9. Solidity 문자열 비교

솔리디티 에서는 문자열 비교가 타 언어 처럼 지원하지 않는다.
문자열을 비교할때는 bytes 형 으로 형 변환뒤 사용을 해야한다 예시👇👇👇
contract lec24{
    
    event CountryIndexName(uint256 indexed _index, string _name);
    string[] private countryList = ["South Korea","North Korea","USA","China","Japan"]; 
    
    
    //Taiwan
    function linearSearch(string memory _search) public view returns(uint256,string memory){
        
        for(uint256 i=0; i<countryList.length; i++){
        
            if(keccak256(bytes(countryList[i])) == keccak256(bytes(_search))){
                return (i,countryList[i]);
            }
        }
        
        return(0,"Nothing");
        
    } 

}

10. Solidity 예외처리

솔리디티에서도 예외처리가 가능하다 예외처리는 총 4가지 정도가 있는데 한번 알아보자
  1. assert : gas를 다 소비한 후, 특정한 조건에 부합하지 않으면 에러를 발생시킨다.
  2. revert : 조건없이 에러를 발생시키고 gas를 환불 시켜준다.
  3. require : 특정한 조건에 부합하지 않으면 에러를 발생시키고, gas를 환불 시켜준다.
//3000000 gas 
    function assertNow() public pure{
        assert(false); // test 
    }  
//21322 gas
    function revertNow() public pure{
        revert("error!!"); // if or require = if + revert;
    }
    
//21338 gas
    function requireNow()public pure{
        require(false,"occurred");
    }
    
try/catch
try/catch문 안에서, assert/revert/require을 통해 에러가 난다면 catch는 에러를 잡지를 못하고, 개발자가 의도한 줄 알고 정상적으로 프로그램이 끝난다.
단. try/catch문 밖에서 assert/revert/require을 통해 오류가 난다면 catch로 오류를 잡고 에러를 핸들링 할 수 있다.
try/catch의 종류
  1. catch Error : revert 나 require을 통해 생성된 에러를 잡는 기능
  2. catch Panic : assert 를 통해 생성된 에러가 날 때 에러를 잡는 기능
  3. catch : 모든 에러를 잡아내는 기능
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
        
        try mathInstance.division(_num1, _num2) returns(uint256 value){
            return(value,true);
            
        } catch Error(string memory _err) {
            
            emit catchErr("revert/require",_err);
            return(0,false);
            
        } catch Panic(uint256 _errorCode) {

            emit catchPanic("assertError/Panic",_errorCode);
            return(0,false);
        } catch (bytes memory _errorCode) {

            emit catchLowLevelErr("LowlevelError",_errorCode);
            return(0,false);
        }
        
        
        
    } 

11. Payable

Payable 이란 ? : 이더/토큰과 상호작용시 필요한 키워드 기능이다.
즉 send,trnasfer,call을 이용하여 , 이더를 보내거나 받을때 Payable 키워드가 필요하다.
(msg.sender : 이더를 보내준 주소
msg.value : 이더를 받은 값

msg.sender.balance : 이더를 보내고 난 후 잔액)
이더를 보내는 3가지 방법
  1. send : 2300gas를 소비, 성공여부는 true/false를 반환
  2. transfer : 2300gas를소비 , 실패시 에러를 발생한다.
  3. call : 가번젹인 gas소비, 성공여부는 true/false 반환
    재진입 (reentrancy) 공격 위험성이 있다 하지만 이스탄불 하드포크 이후로 가스 소비량이 비싸져 2019년 12월 이후 call 사용을 추천한다.
bool chk = "이더를 보낼주소".send(msg,value); // send를 통한 이더 송금
"이더를 보낼주소".transfer(msg.value); // transfer 를 통한 이더 송금
(bool chk ,"반환받을 인자") = "이더를 보낼주소".call.value(msg.value).gas("가스크기 지정 안해두됨")("함수를 호출할 이름"); // 0.6 버전 아래의 call 함수방법
(bool chk ,"반환받을 인자") = "이더를 보낼주소".call{value:msg.value,gas:100}('함수를 호출할 이름'); // 0.6 버전 이상 call 함수방법

12. 함수제어자

modifier checkError{
	require(msg.sender == owner);
	_; // "_;" 이 함수가 동작할 부분을 명시해 주는 것이다.
}
// 이 함수는 스마트 컨트랙을 배포시킨 사용자만 함수를 실행 시킬 수 있으며 다른 사용자가
// 이 함수를 호출시 require의 걸려 에러를 발생시키는 구문이다.

함수제어란 : 함수가 실행되기전에 요구조건을 만족시키는지 확인하는 작업을 해준다. 요구조건을 확인하기 위해서 function함수에도 modifier 이름을 명시를 해줘야한다.

13.fallback

fallback이란 : fallback은 함수 이름 그대로 대비책 함수 입니다.

특징
  1. 무기명 함수이다.
  2. external 필수
  3. payable
사용이유
  1. 스마트 컨트랙이 이더를 받을 수 있게 해준다.
  2. 이더를 받고난 후 어떠한 행동을 취하게 할 수 있다.
  3. call함수로 없는 함수가 불려질때, 행동을 조치할 수 있다.
fallback 함수는 0.6 이전과 이후로 나눠집니다.

0.6 이후
receive : 순수하게 이더를 받을때만 작동하는 곳
fallback : 함수를 실행하면서 이더를 보낼때, 불려진 함수가 없을 때 작동하는 곳

contract Bank{
    event JustFallBackWithFunds(address _from, uint256 _value, string messge);
    event RecevieFallback(address _from, uint256 _value, string messge);
    event JustFallbackWithFunds(address _from,string messge);

    receive() external payable{ // receive 는 이더를 받을 때 호출 되는 곳 이기 때문에 상호작용을 위해서 payable이 붙는다.
    // receive 는 이더를 받을 때만 수행하는 곳 이다.
        emit RecevieFallback(msg.sender, msg.value, "Bank Give Ether!!!");

    }

     fallback() external payable{
         // fallback 은 함수를 호출했을때 또는 함수를 호출 했지만 함수가 없을 경우에 수행하는 곳 이다.
        emit JustFallbackWithFunds(msg.sender, "Sender Give me Ether");
    }
}

0.6 이전
fallback 은 하나로 통합이 되어 움직입니다.

contract Bank{
    event JustFallBackWithFunds(address _from, uint256 _value, string messge);
    event RecevieFallback(address _from, uint256 _value, string messge);
    event JustFallbackWithFunds(address _from,string messge);

    // ~0.6 버전 이전일때
    function() external payable{ // 이더와의 상호작용을 위해서 payable 이더를 받고나서 일을 해야하기때문에 external 을 명시하였다.(외부에서 이 함수를 부르기 때문에 external을 사용한 것 이다.)
      emit JustFallBackWithFunds(msg.sender,msg.value,"JustFallbackFunds is called");
    }

   // 0.6 ~ 버전일때 두가지로 나눠진다 fallback 과 receve로
    fallback() external{
        emit JustFallbackWithFunds(msg.sender, "Sender Give me Ether");
    }

   
}

14. Call & DelegateCall

call 특징
  1. 송금하기
  2. 외부 스마트 컨트랙 함수 호춣
  3. 가변적 gas
  4. 이스탄불 하드포크 이후 2019년 12월 이후 ,gas 가격 상승에 따른 call 사용권장
    re-entrancy(재진입) 공격위험이 있기에,Checks_Effects_Interactions_pattern 사용
// call 함수로 외부 스마트 컨트랙의 함수를 호출 하는 법
contract caller{
    event calledFunction(bool _success, bytes _output);
   
    // 외부 스마트 컨트랙 함수 부르기 
    function callMethod(address _contractAddr,uint256 _num1, uint256 _num2) public{
        
        (bool success, bytes memory outputFromCalledFunction) = _contractAddr.call(
             abi.encodeWithSignature("addNumber(uint256,uint256)",_num1,_num2)
            );
              
        require(success,"failed to transfer ether");
        emit calledFunction(success,outputFromCalledFunction);
    }
}

call 기능을 이용해 외부 스마트 컨트랙 함수를 호출을 할려면 함수를 부를 곳에
abi.encodeWithSignature("호출할 함수이름",'인자') 를 명시하면 호출이 가능하다!!

Call 과 DelegateCall의 차이점
  1. msg.sender 본래의 스마트컨트랙 사용자를 나태낸다.
  2. delegatecall이 정의된 스마트컨트랙이 외부 컨트랙의 함수들을 마치 자신의 것 처럼 사용이 가능하다 (실질적인 값도 자신의 컨트랙에 저장된다.)
  3. 조건 : 외부 스마트컨트랙과 자신의 컨트랙은 같은 변수를 갖고 있어야 한다.
    (upgradable smart contract 용도 : 블록체인 특성상 한번 Deploy가 된 컨트랙들은 변경이나 수정 삭제가 불가능 하다 부득이 하게 함수의 로직을 바꿔야 할때는 새로 만들어 배포를 해야 하는데 DelegateCall을 사용하면 중요 로직만 다시 생성해 변경만 해도 되는 이점이 있다. )
contract add{ // call 로 호출시 add의 num의 값이 증가하지만
		// delegatecall로 호출시 caller의 num의 값이 증가하는 것을 알 수 있다.
    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");
    }
}
profile
꿈틀대는꼬마개발자

0개의 댓글