개발공부일지
BlockChain - Proxy contract, EVM OPCODE, assembly 본문
목차
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구문작성도 가능함
'BlockChain' 카테고리의 다른 글
BlockChain - gas paymaster, meta transaction, ethers (0) | 2024.02.13 |
---|---|
BlockChain - factory contract, DAO, modifier, checks-effects-interactions pattern, mutex (0) | 2024.02.07 |
BlockChain - ERC721, IPFS, pinata (0) | 2024.02.01 |
BlockChain - token, transfer (0) | 2024.01.31 |
BlockChain - 메타마스크 네트워크 추가하기 (0) | 2024.01.31 |