Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발공부일지

BlockChain - Proxy contract, EVM OPCODE, assembly 본문

BlockChain

BlockChain - Proxy contract, EVM OPCODE, assembly

보람- 2024. 2. 8. 15:59

목차

1. Proxy 

2. EVM OPCODE

3. Proxy 패턴 ① 

4. Proxy 패턴 ②

5. Proxy 패턴 ③


 

 

 

 

 

1. Proxy 

 

- 블록체인의 장점이 불변성인데, 컨트랙트 배포하면 수정이 불가능함
- 그래서 사용하는 proxy 패턴 (버그발생이나 이벤트 추가 등등 수정할 일이 생겼을 경우)
- 상태는 proxy에 보관하고, 로직부분은 대리호출을 담당하는 컨트랙트에서 담당

 

- 클라이언트가 직접 컨트랙트에 요청하는게 아니라 프록시에 요청 (프록시 CA주소가 바뀌지않음)
- 사용하는 컨트랙트를 수정하는일이 생겼다면 프록시에 바뀐 컨트랙트CA주소를 참조해서 사용
- 클라이언트는 프록시 CA주소로 요청하면됨

 

- client → proxy contract(대리 호출) → 사용할 컨트랙트
     →  proxy 상태가 업데이트 되는데 client에게 전달가능함

 

 

 

2. EVM OPCODE


https://ethereum.org/ko/developers/docs/evm/opcodes

 

- EVM : stack machine으로 push, pop이 가능한 데이터 구조를 가지고 있음
- opcode : LIFO 데이터 구조 (last in first out)

- 어셈블리구문을 사용해서 로우 레벨 솔리디티 작성할수있음
- 메모리, 스토리지에 직접 접근이 가능함
- 주소참조, 메모리 효율적으로 사용해서 가스비 절감!
  - 수학적 연산이나 암호화 부분 로직등을 작성

// 1 2 ADD

contract ADDtest{
  function add() public {
    assembly {
      // assembly 코드블록
      // 변수 선언하는 let (js아님)
      let result := add(1,2) // add === opcode, 메모리 주소로 값을 할당
      // mstore 변수를 주소에 저장, 컨트랙트 메모리 영역에 저장됨
      mstore(0x0, result)
      return(0x0, 32) // 0x0주소에 32바이트 반환
    }
  }
}

 

** 산술 : ADD(더하기), SUB(빼기), MUL(곱하기), DIV(나누기), MOD(나머지)

** 데이터 : CALLDATACOPY, CODECOPY

** 생성이나 호출 : CREATE, CALL, CALLCODE, DELEGATECALL(대리호출), STATICALL


*** CALLDATACOPY 데이터복사해두었다가 DELEGATECALL 대리호출 사용해볼것


 

 

 

3. Proxy 패턴 ① 

 

 

- count.sol파일과 proxy.sol 파일 작성하기


- bytes32 : string보다 작은 데이터를 효율적으로 사용(고정되는 해시 데이터)
- constant : 상수의 데이터 (const)
- keccak256 : hash화 해주는

- bytes32 public constant IMPL_SLOT = bytes32(uint(keccak256("IMPL")) -1); 

   : 음수로 떨어질수없음 (값이 있는지 확인하는 용도, 없으면0이니까) CA주소 체크

- bytes32 public constant ADMIN_SLOT= bytes32(uint(keccak256("ADMIN")) -1); 

  : 배포자가 로직 업그레이드 담당

- SLOT : 컨트랙트 메모리 공간
- library : 코드 모듈화, 자주사용하는 기능 라이브러리
- pure : 상태변수 접근X, 연산만 가능함
- internal : 상속받은 컨트랙트에서 호출 가능 (외부X)


- pointer.slot := _slotAddress : 메모리 주소값을 얻기위해서 assembly에서는 ; 붙이지않음!
  - storage.slot에 CA주소를 직접넣어 메모리에 넣어두어 리턴해서 사용하려고
- Slot.getAddressSlot(ADMIN_SLOT) : 메모리주소에 AddressSlot 객체의 값에 owner를 넣음 (메모리 직접 참조)
- Slot.getAddressSlot(IMPL_SLOT).value : CA주소 담아둘 메모리 공간


- recive() : 이더를 보냈을때 (transfer)

- fallback() : 메시지를 받았을때, 그리고 메서드명이 매핑이 안될때

   - external 필수로 작성, 단한번만 작성할수있음

- 프록시 컨트랙트는 메서드가없음 → 알고있는 CA로 대리호출 요청해야함(메서드 식별자 4자리로)

- client는 프록시컨트랙트를 count컨트랙트로 알고있는것이고
   → increment 요청했다면 프록시 fallback 함수를 호출해서 count컨트랙트에 요청하는것!

 

더보기
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract Proxy {
  bytes32 public constant IMPL_SLOT = bytes32(uint(keccak256("IMPL")) -1);

  bytes32 public constant ADMIN_SLOT= bytes32(uint(keccak256("ADMIN")) -1);

  constructor() {
    setOwner(msg.sender);
  }

  modifier onlyOwner() {
    require(msg.sender == getOwner());
    _;
  }

  function getOwner() private view returns(address) {
    return Slot.getAddressSlot(ADMIN_SLOT).value;
  }

  function setOwner(address owner) private {
    Slot.getAddressSlot(ADMIN_SLOT).value = owner;
  }

  function getImpl() private view returns(address) {
    return Slot.getAddressSlot(IMPL_SLOT).value;
  }
  function setImpl(address _CA) public onlyOwner {
    Slot.getAddressSlot(IMPL_SLOT).value = _CA;
  }

  function getAdmin() external view returns(address) {
    return getOwner();
  }

  function getEImpl() external view returns(address) {
    return getImpl();
  }

  function delegate(address impl) private {
    assembly {
      calldatacopy(0, 0, calldatasize())
      let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
      returndatacopy(0, 0, returndatasize())

      switch result
      case 0 {
        revert(0, returndatasize())
      }
      default {
        return(0, returndatasize())
      }
    }

  }

  fallback() external payable {
   delegate(getImpl());
  }

  receive() external payable{
    delegate(getImpl());
  }
}

library Slot {
  struct AddressSlot {
    address value;
  }

  function getAddressSlot(bytes32 _slotAddress) internal pure returns(AddressSlot storage pointer){
    assembly {
      pointer.slot := _slotAddress
    }
  }

}

 

 

 

 

 

4. Proxy 패턴 

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract Count {
  uint public count;
  function increment() public {
    count += 1;
  }
}

 

- Count contract ver1으로 증가하는 함수 컨트랙트 배포하고

- Proxy contract 작성해서 배포하기

- Proxy 컨트랙트에 Count CA주소를 넣어주기 → Proxy 배포 CA주소로 Count abi로 실행 (remix에서 At address)  → Count 컨트랙트 나와서 증가함수로 count를  1씩 증가시킬수 있음 ( count : 1)

  (기존 Count contract ver1의 count 상태변수는 변하지않음 count : 0 )

 

 

 

 

 

 

 

5. Proxy 패턴 ③

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract Count {
  uint public count;
  function increment() public {
    count += 1;
  }
   function decrement() public{
    count -= 1;
  }
}

 

- Count contract ver2 으로 기존 Count 컨트랙에 감소하는 함수 추가해서 재배포

- 기존 Proxy 컨트랙트에 새로운 Count CA 주소 넣어주기 (대리호출 요청)

- 기존 Proxy CA 주소로  새로운 Count abi로 실행하면 (remix에서 At address) → Count 컨트랙트 나와서 증가함수에 업데이트한 감소함수까지 실행할수있음 (기존 proxy 상태변수를 가지고있음! count : 1 ) 

 

 

 

 

 


assembly

- calldatacopy() : 0의 위치 데이터를 복사 시작, 얼마나 복사할지 calldatasize 데이터크기 확인

- delegatecall() : 대리호출 요청, proxy의 상태를 가지고 요청을 보냄
   - gas() : 트랜잭션을 처리할수있는 가스량, 블록에서 처리가능한 가스량
   - implementation : 로직을 대리호출할 CA 주소
   - 0, calldatasize() : 복사해둔 데이터 (로직부분)
   - 0, 0 : 반환값의 위치, 반환값의 크기 (사용하지않거나 크기를 모를경우엔 0이라 적기)

- retrundatacopy() : 대리호출한 이후 컨트랙트 반환데이터를 메모리 0번 위치에 시작점부터 사이즈 만큼 복사
- revert되거나 그럴때 switch구문작성도 가능함