체인의정석

스마트 컨트렉트 프록시 구조 - 기본 구조 학습 본문

블록체인/Solidity

스마트 컨트렉트 프록시 구조 - 기본 구조 학습

체인의정석 2022. 3. 18. 14:45
728x90
반응형

코드를 계속해서 분석하다 보니 결국 릴레이어를 사용하는 모델에서는 프록시 구조를 알지 못하고서는 코드를 70%이상 이해하기 어려웠다. 따라서 프록시 구조에 대한 자세한 학습을 하고 이후에 더 깊은 분석을 해보려고 한다.

 

학회분들의 조언에 따라 학습은 오픈제플린 및 트러플 등에서 시작해보기로 하엿다.

 

https://blog.openzeppelin.com/proxy-patterns/

 

Proxy Patterns - OpenZeppelin blog

One of the biggest advantages of Ethereum is that every transaction of moving funds, every contract deployed, and every transaction made to a contract is immutable on a public ledger...

blog.openzeppelin.com

프록시 패턴에 대한 글로 설명이 잘되어 있는 글을 먼저 보겠다.

 

이미지의 모든 출저는 위의 링크이다.

https://blog.openzeppelin.com/proxy-patterns/

먼저 프록시 패턴의 경우 프록시 컨트렉트에 스토리지 데이터를 저장하고 연결된 로직 부분에 대한 컨트렉트는 계속해서 바꿀 수 있게 만든 배포 구조이다.

 

프록시 배포의 대표적인 패턴은 3가지가 있다.

 

  1. Inherited Storage
  2. Eternal Storage
  3. Unstructured Storage

상속받은 스토리지, 영구적인 스토리지, 비구조화 스토리지의 3 패턴이다.

3개의 패턴 모두 저수준의 delgate call을 발생시킨다.

솔리디가 delegate function call을 한다고 하더라도 오직 true/false를 리턴하여 call이 성공하였는지 아닌지를 리턴한다. 그리고 데이터를 리턴하지 않는다.

 

2가지 키 컨셉이 있다.

1. 컨트렉트에 보내는 call이 지원되지 않는 함수일경우 fallback 함수는 false를 리턴한다. 

2. A 컨트렉트에서 다른 B컨트렉트로 delegate call을 보낼 때 컨트렉트 B의 코드를  A의 문맥에 맞추어서 실행한다. 이 의미는 msg.value 와 msg.sender의 값들이 그대로 유지되고 모든 스토리지의 변경사항이 A의 스토리지에 영향을 끼친다.

 

따라서 코드를 분석할때 msg.value와 msg.sender 가 프록시 컨트렉트를 거치더라도 동일하게 유지되면서 간다고 보면 된다. 그리고 스토리지 변화는 도착하는 최종 컨트렉트, 로직만 담겨져있는 컨트렉트에 저장되기 때문에 실행 로직에 대한 분석은 도착하는 컨트렉트를 보면 된다.

 

[assembly {
    let ptr := mload(0x40)
    //포인터 변수로 지정
    calldatacopy(ptr, 0, calldatasize)
    let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
    //result 변수 선언에 delegatecall을 사용하여 정의
    //delegatecall은 call과 같지만, caller랑 call value를 유지시켜주는 차이가 있다.
    let size := returndatasize
    //size에 대한 변수 선언, returndatasize는 마지막으로 리턴된 데이터의 사이즈를 반환
    returndatacopy(ptr, 0, size)
	//0 번의 위치에 있는 데이터 중 size만큼을 ptr의 메모리에 적재한다.
    switch result
    case 0 { revert(ptr, size) }
    //조건 문 false인 경우 해당 포인터에 revert 리턴
    default { return(ptr, size) }
    //아닌 경우 디폴트로 결과 값 해당 포인트에 리턴
 }

 

mload(p)   F mem[p..(p+32))

일단 ptr에 메모리를 확보한다.

calldatacopy(t, f, s) - F copy s bytes from calldata at position f to mem at position t

calldatacopy는 s를 복사한다. 복사한 s는 t라는 메모리 위치에 들어가게 되며, f의 위치에 있는 call data를 복사해둔다.

=> ptr의위치에다가 calldatasize에 해당하는 콜하는 데이터만큼의 크기 부분을 복사시킨다.

returndatacopy(t, f, s) - B copy s bytes from returndata at position f to mem at position t

=> 위와 같지만 리턴 데이터라는 차이가 있다.

 

그럼 자세하게 살펴보도록 하겠다.

 

msg.data는 bytes 형태의 타입이기 때문에 다양한 사이즈가 있다.

실제 데이터를 추출하기 위해서는 0x20부터 추출을 하는게 일반적이다.

이러한 작업을 실행할때 2개의 opcode를 사용해야하는데

여기서는 msg.data에 calldatasize를 사용하여 사이즈를 가져오고

cappdatacopy를 사용하여 해당 사이즈에 해당되는 데이터를 ptr 변수에 복사시킬 것이다.

 

ptr 변수를 선언할 때 사용한 0x40 위치에 있는 메모리 공간은 자유롭게 사용가능한 메모리 포인터를 의미한다. 따라서 메모리에 직접 저장할 때마다 해당 어디에 저장할지를 논의 해야 한다. 이는 0x40에 있는 값들은 체크하여 결정된다. 이렇게 ptr을 설정해 두면 변수를 설정하도록 허용이 되었기 때문에 calldatacopy를 사용하여 calldata의 사이즈인 calldata ( 0 번부터 calldata만큼의 범위를) 복사하여서  변수로 둘 수 잇다.

let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)

 

다음으로 그 다음줄을 살펴보겠다.

let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)

 파라미터를 보면

 

gas: 함수를 실행하는 데 필요한 가스를 전달합니다.
_impl: 우리가 호출하는 논리 계약의 주소
ptr: 데이터가 시작되는 메모리 포인터
calldatasize: 우리가 전달하는 데이터의 크기.

논리 계약 호출에서 반환된 값을 나타내는 데이터 출력의 경우 0입니다. 
이것은 우리가 아직 데이터 출력의 크기를 알지 못하기 때문에 사용하지 않고 변수에 할당할 수 없습니다.
나중에 returndata opcode를 사용하여 이 정보에 계속 액세스할 수 있습니다.

사이즈 아웃의 경우 0입니다. 
다른 계약을 호출하기 전에 크기를 알지 못했기 때문에 데이터를 저장할 임시 변수를 만들 기회가 없었기 때문에 사용되지 않습니다. 
나중에 returndatasize opcode를 호출하여 다른 방법을 사용하여 이 값을 얻을 수 있습니다.

 

이렇게 result라는 변수를 지정시킵니다.

let size := returndatasize

그 다음줄을 보면 0으로 입력한 값에 대하여 사이즈를 지정해 줍니다. 이 사이즈는 데이터를 카피하는데 사용됩니다.

returndatacopy(ptr, 0, size)

마지막으로 스위치 문을 써서 에러나 예외상황에 대한 처리를 진행합니다.

이렇게 데이터를 중간 통로를 통해서 왔다 갔다 할 수 있게 도와주는 구조입니다.

 

이제 3가지 패턴에 대해서 알아보겠습니다.

 

주요한 프록시 컨트렉트의 구조 포인트는 스토리지 할당을 어덯게 할지의 여부 입니다.

 

 

+) 위의 어셈블리 코드를 분석하기 위해서는 

https://docs.soliditylang.org/en/v0.4.21/assembly.html

 

Solidity Assembly — Solidity 0.4.21 documentation

Docs » Solidity in Depth » Solidity Assembly Edit on GitHub Solidity Assembly Solidity defines an assembly language that can also be used without Solidity. This assembly language can also be used as “inline assembly” inside Solidity source code. We s

docs.soliditylang.org

해당 문서를 자세히 살펴봐야 합니다.

 

글쓴이의 유튜브 보러가기 

https://www.youtube.com/channel/UCHsRy47P2KlE749oAAjb0Yg

 

체인의정석

약력 현) 블록체인 개발자 前 블록워터 테크놀로지, 스마트컨트렉트 개발자 前 위데이터랩(주) 기획,마케팅 팀장 , 블록체인팀 선임연구원 홍익대학교 경영학 전공, 컴공 부전공 서강대학교 정

www.youtube.com

728x90
반응형
Comments