Proxy Patterns

CHOYEAH·2024년 2월 6일
0

Transparent

개념

투명 프록시 패턴은 프록시를 통해 로직 컨트랙트에 접근하는 방식이나. 업그레이드 관리 로직이 프록시 컨트랙트에 포함되어 있다. (관리자만이 컨트랙트를 업그레이드할 수 있도록 함)
로직 컨트랙트 주소를 업그레이드하는 함수는 프록시, 로직 두 컨트랙트에 존재하나 사용자 어카운트와 어드민 어카운트의 함수 호출 대상 컨트랙트를 다르게 함으로써 함수 충돌 이슈를 해소한다.
어드민 계정은 프록시 컨트랙트로, 사용자 계정은 로직 컨트랙트를 호출하도록 되어있다.

장점

업그레이드 프로세스가 단순하고 이해하기 쉬움.
사용자와 개발자 모두 같은 주소를 사용하여 컨트랙트와 상호작용.

단점

더 많은 가스 비용이 발생할 수 있음.
프록시와 구현체 간의 명확한 구분이 필요.

적합한 상황

사용자와 개발자 모두가 동일한 인터페이스를 사용해야 하는 경우.
업그레이드 관리를 중앙에서 통제하고자 할 때.

부적합한 상황

가스 비용 최소화가 중요한 프로젝트.
업그레이드 프로세스에 더 많은 유연성이 필요한 경우.

주의사항

관리자 주소의 보안이 매우 중요함.
업그레이드 시 데이터 호환성을 확인해야 함.

예시 코드

// Transparent Proxy 패턴 예시 코드

pragma solidity ^0.8.0;

contract LogicContract {
    uint256 public data;

    function setData(uint256 _data) external {
        data = _data;
    }
}

contract TransparentProxy {
    address public logicContract;
    address public owner;

    constructor(address _logicContract) {
        logicContract = _logicContract;
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function");
        _;
    }
    
    function _beforeFallback() internal virtual override {
        require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
        super._beforeFallback();
    }

    // fallback 함수를 사용하여 로직 컨트랙트의 함수를 호출
    fallback() external payable {
        address target = logicContract;
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(ptr, 0, size)

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

    // 로직 컨트랙트를 업그레이드하는 함수
    function upgradeLogic(address _newLogicContract) external onlyOwner {
        logicContract = _newLogicContract;
    }
   
}

TransparentProxy 컨트랙트는 로직 컨트랙트와 사용자 간의 인터페이스 역할. 사용자는 TransparentProxy를 통해 로직 컨트랙트의 함수를 호출할 수 있으며, TransparentProxy는 호출된 함수를 로직 컨트랙트로 전달. 사용자는 로직 컨트랙트를 직접 호출하는 것처럼 투명하게(transparently) 함수를 호출할 수 있음. 또한, TransparentProxy 컨트랙트의 소유자만이 로직 컨트랙트를 업그레이드할 수 있도록 onlyOwner modifier가 적용.

비콘(Beacon)

개념

비콘 프록시 패턴은 여러 프록시 컨트랙트가 하나의 비콘 컨트랙트를 참조하여 로직 컨트랙트의 주소를 얻는 방식. 비콘은 로직 컨트랙트의 주소를 저장하고, 모든 프록시는 이 비콘을 통해 업그레이드된 로직에 접근.

장점

여러 프록시 컨트랙트의 업그레이드를 중앙에서 관리할 수 있음.
업그레이드 시 가스 비용이 절약됨.

단점

비콘 자체의 보안과 관리가 중요함.
모든 프록시가 동시에 업그레이드되어야 함.

적합한 상황

동일한 로직을 사용하는 다수의 프록시 컨트랙트가 있는 경우.
중앙에서 여러 컨트랙트의 업그레이드를 효율적으로 관리하고자 할 때.

부적합한 상황

각 프록시 컨트랙트가 서로 다른 로직을 요구하는 경우.
업그레이드 과정에서 개별 컨트랙트의 특수한 처리가 필요한 경우.

주의사항

비콘의 보안과 관리에 주의해야 함.
모든 프록시 컨트랙트가 동일한 업그레이드를 받게 되므로 주의 깊게 관리 필요.

사용 예시

다수의 NFT 컨트랙트가 동일한 로직을 공유하는 경우.

예시 코드

// Beacon contract - 프록시와 원본 컨트랙트 간의 상호작용을 관리
contract Beacon {
    address public implementation;

    // 프록시의 로직 컨트랙트를 설정하는 함수
    function setImplementation(address _implementation) external {
        implementation = _implementation;
    }
}

// Logic contract - 실제 로직을 포함하는 스마트 컨트랙트
contract LogicContract {
    uint256 public data;

    // 상태를 변경하는 함수
    function setData(uint256 _data) external {
        data = _data;
    }

    // 상태를 조회하는 함수
    function getData() external view returns (uint256) {
        return data;
    }
}

// BeaconProxy contract - 사용자와 프록시 사이의 인터페이스 역할
contract BeaconProxy {
    address public beacon;

    constructor(address _beacon) {
        beacon = _beacon;
    }

    fallback() external {
        address _impl = Beacon(beacon).implementation();
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let success := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
            let retSz := returndatasize()
            returndatacopy(ptr, 0, retSz)
            switch success
            case 0 {
                revert(ptr, retSz)
            }
            default {
                return(ptr, retSz)
            }
        }
    }
}

Beacon 컨트랙트는 프록시와 로직 컨트랙트 간의 상호작용을 관리, 프록시 컨트랙트는 Beacon을 통해 실제 로직이 구현된 컨트랙트로 주소를 가져와 로직 컨트랙트에 델리게이트 콜

UUPS(Universal Upgradeable Proxy Standard) (EIP-1822)

개념

UUPS 패턴은 업그레이드 로직을 구현 컨트랙트 자체에 포함시키는 방식으로 현재 프록시 패턴에서 가장 흔히 쓰이는 패턴이다. (구현 컨트랙트가 자신을 업그레이드할 수 있는 기능을 내장하고 있음) 이는 프록시 컨트랙트가 업그레이드 로직을 가지지 않게 하여, 구현 컨트랙트에서만 업그레이드를 관리. 프록시 컨트랙트는 단순히 구현 컨트랙트로의 요청을 전달하는 역할만 수행한다. 오픈제플린에서도 Transparent가 아닌 UUPS 패턴 사용을 권장하고 있다.

장점

구현 컨트랙트에서 업그레이드를 관리하기 때문에, 가스 비용이 절약됨.
업그레이드 로직과 구현 로직의 분리가 불필요.

단점

구현 컨트랙트에 추가적인 복잡성이 생김.
잘못된 업그레이드가 시스템 전체에 영향을 줄 수 있음.

적합한 상황

업그레이드 프로세스를 보다 유연하게 관리하고 싶은 경우.
가스 비용 절약이 중요한 프로젝트.

부적합한 상황

구현 컨트랙트의 복잡성을 최소화하고 싶은 경우.
업그레이드 로직을 별도로 관리하고자 하는 경우.

주의사항

구현 컨트랙트의 업그레이드 로직에 대한 충분한 테스트가 필요함.
잘못된 업그레이드가 전체 시스템에 큰 영향을 줄 수 있으므로 주의 필요.

사용 예시

가스 비용 최적화가 중요한 대규모 애플리케이션.

예시코드

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

// 필요한 OpenZeppelin 컨트랙트를 가져옵니다.
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// 구현 컨트랙트
contract MyContractV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
    uint256 private value;

    // 초기화 함수
    function initialize(uint256 _value) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        value = _value;
    }

    // 업그레이드 권한 체크
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    // 비즈니스 로직 예시: 값 설정
    function setValue(uint256 _value) public {
        value = _value;
    }

    // 비즈니스 로직 예시: 값 조회
    function getValue() public view returns (uint256) {
        return value;
    }
}


프록시 컨트랙트 배포와 업그레이드는 OpenZeppelin 플러그인을 사용.

프록시 컨트랙트는 OpenZeppelin의 플러그인을 사용하여 배포. deployProxy 함수를 사용하여 프록시와 초기 구현 컨트랙트를 배포, 초기화.

const { deployProxy } = require('@openzeppelin/hardhat-upgrades');

async function main() {
    const MyContractV1 = await ethers.getContractFactory("MyContractV1");
    const myContract = await deployProxy(MyContractV1, [42], {initializer: 'initialize'});
    
    console.log("MyContractV1 deployed to:", myContract.address);
}

main().then(() => process.exit(0)).catch(error => {
    console.error(error);
    process.exit(1);
});

MyContractV1을 초기 구현 컨트랙트로 사용하여 프록시를 배포하고, 초기화 매개변수로 42를 전달. 배포 후, 프록시 컨트랙트 주소가 콘솔에 출력됨.

업그레이드

const { ethers, upgrades } = require("hardhat");

async function main() {
    const MyContractV2 = await ethers.getContractFactory("MyContractV2");
    const myContract = await upgrades.upgradeProxy(proxyAddress, MyContractV2);
    console.log("MyContract upgraded to V2 at:", myContract.address);
}

main().then(() => process.exit(0)).catch((error) => {
    console.error(error);
    process.exit(1);
});

4. Diamond (EIP-2535)

개념

다이아몬드 패턴은 여러 기능을 갖는 스마트 컨트랙트를 하나의 주소(다이아몬드)에 결합할 수 있도록 설계된 패턴. 이를 통해 스마트 컨트랙트의 기능을 개별적으로 추가, 제거, 업데이트할 수 있다.

다이아몬드 패턴의 주요 구성 요소

  • 다이아몬드(Diamond): 사용자와 상호 작용하는 컨트랙트. 다이아몬드는 여러 facet의 기능을 사용할 수 있도록 프록시 역할을 한다.
  • Facet: 특정 기능 또는 로직을 구현한 스마트 컨트랙트를 의미, 각 facet은 다이아몬드의 특정 기능을 담당.
  • 다이아몬드 컷(DiamondCut): 다이아몬드에 facet을 추가, 제거, 또는 교체하는 과정을 관리하는 로직을 포함한 컨트랙트나 함수. 이를 통해 다이아몬드의 기능을 업그레이드하거나 수정할 수 있음.
  • DiamondLoupe: 다이아몬드의 Facet과 함수에 대한 정보를 제공.
  • Ownership: 다이아몬드의 소유권 관리를 담당.

기능을 등록하고 사용자가 사용할 수 있게 되는 과정

  • Facet 등록: diamondCut 함수를 호출하여 새로운 facet(기능 구현 컨트랙트)을 다이아몬드에 추가하거나, 기존 facet의 기능을 수정하거나 제거. 이 함수 호출은 주로 다이아몬드의 소유자나 관리자에 의해 수행된다. 어떤 facet 주소에서 어떤 함수 선택자(function selectors)를 추가, 교체, 또는 제거할지는 FacetCut 구조체를 통해 이뤄진다.

  • 함수 선택자 매핑: 다이아몬드 패턴에서는 함수 선택자(함수의 시그니처를 나타내는 4바이트 해시)를 기반으로 요청을 적절한 facet으로 라우팅합니다. diamondCut을 통해 facet이 추가되면, 다이아몬드는 내부적으로 함수 선택자와 해당 facet 주소 간의 매핑을 저장합니다. 이 매핑 정보는 함수 호출이 어떤 facet으로 전달되어야 하는지 결정하는 데 사용됩니다.

  • 사용자 호출 처리: 사용자가 다이아몬드 컨트랙트의 함수를 호출하면, 다이아몬드 컨트랙트의 fallback 함수 또는 receive 함수가 실행. 호출된 함수 선택자를 확인하고, 내부적으로 저장된 매핑 정보를 사용하여 해당 함수 선택자에 매핑된 facet으로(컨트랙트 주소) 요청을 전달. delegatecall을 통해 facet의 코드가 다이아몬드 컨트랙트의 컨텍스트(상태 변수 등)에서 실행된다.

  • 함수 실행: 요청이 적절한 facet으로 라우팅되면, facet 내의 해당 함수가 실행된다. 이때, 함수 실행 결과는 다이아몬드 컨트랙트의 상태에 영향을 미침. 함수 실행이 완료되면, 결과(반환 값 또는 이벤트)는 사용자에게 전달된다.

장점

  • 스마트 컨트랙트의 확장성과 유연성 향상.
  • 스마트 컨트랙트의 크기 제한 문제 해결.

단점

  • 구현과 관리의 복잡성 증가.
  • 보안 리스크 관리가 더 중요해짐.

사용에 적합한 상황

  • 복잡하고 확장 가능한 스마트 컨트랙트 시스템 개발에 적합.
  • 모듈식 설계를 필요로 하는 대규모 프로젝트.

사용에 부적합한 상황

  • 단순한 기능만 필요로 하는 소규모 프로젝트.
  • 빠른 개발과 배포를 우선시하는 경우.

주의사항

  • 패싯(facet) 관리와 보안에 주의 필요.
  • 업그레이드 시 충돌 방지를 위한 철저한 테스트 필요.

사용 예시

복잡한 게임 또는 금융 서비스 스마트 컨트랙트.

예시 코드

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

interface IDiamondCut {

    enum FacetAction { Add, Replace, Remove }
    struct FacetCut {
        address facetAddress;
        FacetAction action;
        bytes4[] functionSelectors;
    }

    function diamondCut(FacetCut[] calldata _facetCuts) external;
}

contract DiamondStorage {

    struct FacetAddressAndSelector {
        mapping(bytes4 => address) selectorToFacet;
    }

    function diamondStorage() internal pure returns (FacetAddressAndSelector storage ds) {
        bytes32 position = keccak256("diamond.standard.diamond.storage");
        assembly {
            ds.slot := position
        }
    }
}

contract Diamond is IDiamondCut, DiamondStorage {

    function diamondCut(FacetCut[] calldata _facetCuts) external override {
        FacetAddressAndSelector storage ds = diamondStorage();
        for (uint256 i = 0; i < _facetCuts.length; i++) {
            FacetCut memory _facetCut = _facetCuts[i];
            address _facetAddress = _facetCut.facetAddress;
            for (uint256 j = 0; j < _facetCut.functionSelectors.length; j++) {
                bytes4 selector = _facetCut.functionSelectors[j];
                if (_facetCut.action == FacetAction.Add || _facetCut.action == FacetAction.Replace) {
                    ds.selectorToFacet[selector] = _facetAddress;
                } else if (_facetCut.action == FacetAction.Remove) {
                    ds.selectorToFacet[selector] = address(0);
                }
            }
        }
    }

    fallback() external payable {
        FacetAddressAndSelector storage ds = diamondStorage();
        address facet = ds.selectorToFacet[msg.sig];
        require(facet != address(0), "Function does not exist.");
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

Facet 추가, 교체, 제거: diamondCut 함수는 다이아몬드에 facet을 추가, 교체, 또는 제거하는 로직을 구현합니다. 각 facet에 대한 함수 선택자와 주소 매핑은 FacetAddressAndSelector 구조체를 통해 저장.

함수 호출 라우팅: fallback 함수는 사용자의 모든 호출을 처리하고, 저장된 매핑 정보를 사용하여 적절한 facet으로 요청을 라우팅. 이때 delegatecall을 사용하여 facet의 함수가 다이아몬드 컨트랙트의 상태와 컨텍스트에서 실행되도록 한다.

저장소 접근: diamondStorage 함수는 다이아몬드의 상태를 저장하기 위한 고유한 저장소 슬롯을 제공합. 이는 EIP-1967과 유사한 패턴을 사용하여 충돌을 방지.

이 예시는 다이아몬드 패턴의 기본적인 구현 방법을 보여준다. 실제 사용 시에는 권한 관리(예: diamondCut 함수에 대한 접근 제어), 에러 처리, 그리고 최적화를 포함한 추가적인 기능이 필요할 수 있다. EIP-2535 참고

5. Minimal Proxy (EIP-1167)

개념

미니멀 프록시 패턴은 매우 경량화된 구조로 설계되었으며, 주로 코드 재사용성을 높이고 가스 비용을 절감하기 위한 목적으로 사용. 이 패턴에서 프록시 컨트랙트는 단지 다른 주소(구현 컨트랙트)로의 호출을 위임(delegatecall)하는 역할만 수행. 각 미니멀 프록시 인스턴스는 동일한 구현(로직) 컨트랙트의 코드를 사용하여 작동하지만, 각각의 인스턴스는 독립적인 상태를 유지할 수 있다.

장점

  • 배포 비용이 매우 저렴.
  • 원본 컨트랙트 코드의 재사용을 통한 효율성 증가.

단점

  • 업그레이드 가능성이 제한적.
  • 각 인스턴스 관리가 복잡해질 수 있음.

사용에 적합한 상황

  • 동일한 코드를 사용하되 독립적인 상태를 유지해야 하는 여러 인스턴스 배포에 적합.

사용에 부적합한 상황

  • 각 인스턴스의 독립적인 업그레이드가 필요한 경우. (로직 컨트랙트 주소가 생성자에서 초기화되어 수정 불가)

  • 복잡한 상태 관리 및 로직:

    • 업그레이드 유연성 부족: 미니멀 프록시 패턴은 구현 컨트랙트의 로직을 업그레이드하는 메커니즘을 내장하고 있지 않다. 구현 컨트랙트에 수정 사항이 발생하면, 새로운 구현 컨트랙트를 배포하고, 프록시 컨트랙트(또는 이를 생성하는 팩토리 컨트랙트)를 수정하여 새로운 구현 컨트랙트 주소를 사용하도록 해야 한다. 이 과정은 간단하지 않으며, 각 프록시 인스턴스의 독립적인 업그레이드를 지원하지 않는다.

    • 복잡한 로직의 관리: 복잡한 상태 관리가 필요하거나, 다양한 비즈니스 로직을 포함하는 애플리케이션의 경우, 구현 컨트랙트의 변경이 빈번하게 발생할 수 있다. 미니멀 프록시 패턴은 이러한 변경을 간편하게 관리하고 반영하는 구조를 제공하지 않기 때문에, 복잡한 애플리케이션에서는 관리가 어려울 수 있다.

주의사항

  • 단순성과 제한성 인지:
    미니멀 프록시 패턴의 주요 장점 중 하나는 그 단순성에 있다. 이 단순성은 동시에 제한성을 의미하기도 한다. 특히, 미니멀 프록시를 통해 구현 컨트랙트를 업그레이드하거나 변경하는 과정은 추가적인 구성이나 도구 없이는 직접적으로 지원되지 않는다. 따라서, 사용전 이러한 제한성을 충분히 고려해야 한다.

예시 코드

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

contract Implementation {
    uint public number;

    function setNumber(uint _number) public {
        number = _number;
    }

    function getNumber() public view returns (uint) {
        return number;
    }
}

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

contract MinimalProxy {
    address internal _implementation;

    constructor(address implementationAddress) {
        _implementation = implementationAddress;
    }

    fallback() external payable {
        address _impl = _implementation;
        assembly {
            // calldata를 구현 컨트랙트로 전달
            calldatacopy(0, 0, calldatasize())

            // 구현 컨트랙트로 delegatecall 실행
            let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)

            // 반환 데이터를 복사
            returndatacopy(0, 0, returndatasize())

            // delegatecall이 성공적인지 체크 후 적절한 조치 취하기
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

6. Eternal Storage

개념

Eternal Storage 패턴은 데이터를 유지하면서 스마트 컨트랙트의 로직을 업그레이드할 수 있도록 설계된 데이터 저장 방법. 데이터와 로직을 분리하여, 데이터 호환성을 보장.

장점

  • 데이터와 로직의 분리로 업그레이드 용이성 및 데이터 호환성 보장.
  • 장기적인 데이터 보관과 접근성 향상.

단점

  • 추가적인 가스 비용이 발생할 수 있음.
    • 스마트 컨트랙트 간 호출(CALL)은 동일 컨트랙트 내부의 함수 호출보다 더 많은 가스를 소모. 이러한 외부 호출은 상태를 조회하거나 변경할 때마다 발생하며, 컨트랙트의 복잡성과 상호작용 빈도에 따라 가스 비용이 증가할 수 있다.
    • 이터널 스토리지 패턴은 키-값 저장 방식을 사용하여 다양한 타입의 데이터를 관리. 데이터를 저장하거나 접근할 때마다, 해당 키에 대응하는 값에 접근하기 위한 계산이 필요. 이 과정에서 사용되는 키 생성(예: keccak256 해시 함수 사용)과 매핑 접근도 추가적인 가스를 소모.
    • 상태 변수의 값을 변경하는 것은 상대적으로 높은 가스 비용을 발생시키는 연산 중 하나. 특히, 이터널 스토리지 패턴에서는 로직 컨트랙트와 스토리지 컨트랙트 간의 상호작용을 통해 이러한 변경이 이루어지므로, 단일 컨트랙트 내에서 상태를 변경하는 것보다 더 많은 가스가 필요할 수 있다.
  • 구현의 복잡성이 증가.

사용에 적합한 상황

  • 데이터 호환성과 업그레이드 용이성이 중요한 애플리케이션.
  • 장기간 데이터 보관이 필요한 경우.

사용에 부적합한 상황

  • 단순하고 변경 가능성이 적은 데이터 관리가 필요한 경우.
  • 가스 비용 최소화가 중요한 프로젝트.

주의사항

  • 데이터 구조 변경 시 호환성 유지에 주의.
  • 데이터 접근 및 관리 메커니즘 구현에 신중함이 필요.

사용 예시

  • 복잡한 유저 정보를 관리하는 DApp.

예시 코드

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

contract EternalStorage {
    mapping(bytes32 => uint256) internal uintStorage;
    mapping(bytes32 => address) internal addressStorage;

    // 데이터를 설정하는 함수
    function setUint(bytes32 key, uint256 value) external {
        uintStorage[key] = value;
    }

    function setAddress(bytes32 key, address value) external {
        addressStorage[key] = value;
    }

    // 데이터를 가져오는 함수
    function getUint(bytes32 key) external view returns (uint256) {
        return uintStorage[key];
    }

    function getAddress(bytes32 key) external view returns (address) {
        return addressStorage[key];
    }
}

스토리지 컨트랙트는 다양한 타입의 데이터(예: uint, address, bytes, 등)를 저장할 수 있는 범용적인 키-값 저장소 형태를 취한다. 이 저장소는 데이터를 저장하고 검색하는 기능만을 제공하며, 이 데이터를 어떻게 사용할지에 대한 로직은 로직 컨트랙트에서 정의한다.

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

interface IEternalStorage {
    function setUint(bytes32 key, uint256 value) external;
    function getUint(bytes32 key) external view returns (uint256);
}

contract LogicContract {
    IEternalStorage public eternalStorage;

    constructor(address _eternalStorage) {
        eternalStorage = IEternalStorage(_eternalStorage);
    }

    // 데이터를 저장하는 예시 함수
    function saveNumber(uint256 _number) public {
        bytes32 key = keccak256(abi.encodePacked("number"));
        eternalStorage.setUint(key, _number);
    }

    // 저장된 데이터를 가져오는 예시 함수
    function getNumber() public view returns (uint256) {
        bytes32 key = keccak256(abi.encodePacked("number"));
        return eternalStorage.getUint(key);
    }
}

사용자가 로직 컨트랙트가 변경되어도 주소 변환 없이 사용할 수 있도록 하려면, 이터널 스토리지 패턴을 프록시 패턴이나 다른 업그레이드 가능한 컨트랙트 구조와 함께 사용해야 한다.

간단한 프록시 패턴(Simple Proxy Pattern)
업그레이드 가능한 프록시 패턴(Upgradeable Proxy Pattern)
콜렉션 프록시 패턴(Collection Proxy Pattern)
권한 관리 프록시 패턴(Authorization Proxy Pattern)
게이트웨이 프록시 패턴(Gateway Proxy Pattern)
대체 프록시 패턴(Substitute Proxy Pattern)
멀티시그니처 프록시 패턴(Multisignature Proxy Pattern)
마스터-스레이브 프록시 패턴(Master-Slave Proxy Pattern)
보호 프록시 패턴(Protection Proxy Pattern)
자기수리 프록시 패턴(Self-repairing Proxy Pattern)
레이지 로딩 프록시 패턴(Lazy Loading Proxy Pattern)

profile
Move fast & break things

0개의 댓글

관련 채용 정보