본문 바로가기
카테고리 없음

upgradeable contract 하드햇, 오픈제플린 라이브러리 분석 2

by gun_poo 2024. 1. 21.

이어서 분석해보자

return async function upgradeProxy(proxy, ImplFactory, opts: UpgradeProxyOptions = {}) {
    disableDefender(hre, defenderModule, opts, upgradeProxy.name);

    const proxyAddress = await getContractAddress(proxy);

    const { impl: nextImpl } = await deployProxyImpl(hre, ImplFactory, opts, proxyAddress);
    // upgrade kind is inferred above
    const upgradeTo = await getUpgrader(proxyAddress, opts, getSigner(ImplFactory.runner));
    const call = encodeCall(ImplFactory, opts.call);
    const upgradeTx = await upgradeTo(nextImpl, call);

    const inst = attach(ImplFactory, proxyAddress);
    // @ts-ignore Won't be readonly because inst was created through attach.
    inst.deployTransaction = upgradeTx;
    return inst;
  };

1. getUpgrader(proxyAddress, opts, getSigner(ImplFactory.runner))

  async function getUpgrader(proxyAddress: string, opts: UpgradeProxyOptions, signer?: Signer): Promise<Upgrader> {
    const { provider } = hre.network;

    const adminAddress = await getAdminAddress(provider, proxyAddress);
    const adminBytecode = await getCode(provider, adminAddress);

    const overrides = opts.txOverrides ? [opts.txOverrides] : [];

    if (isEmptySlot(adminAddress) || adminBytecode === '0x') {
      // No admin contract: use ITransparentUpgradeableProxy to get proxiable interface
      const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, proxyAddress, log);
      switch (upgradeInterfaceVersion) {
        case '5.0.0': {
          const proxy = await attachITransparentUpgradeableProxyV5(hre, proxyAddress, signer);
          return (nextImpl, call) => proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides);
        }
        default: {
          if (upgradeInterfaceVersion !== undefined) {
            // Log as debug if the interface version is an unknown string.
            // Do not throw an error because this could be caused by a fallback function.
            log(
              `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy at ${proxyAddress}. Expected 5.0.0`,
            );
          }
          const proxy = await attachITransparentUpgradeableProxyV4(hre, proxyAddress, signer);
          return (nextImpl, call) =>
            call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides);
        }
      }
    } else {
      // Admin contract: redirect upgrade call through it
      const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, adminAddress, log);
      switch (upgradeInterfaceVersion) {
        case '5.0.0': {
          const admin = await attachProxyAdminV5(hre, adminAddress, signer);
          return (nextImpl, call) => admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides);
        }
        default: {
          if (upgradeInterfaceVersion !== undefined) {
            // Log as debug if the interface version is an unknown string.
            // Do not throw an error because this could be caused by a fallback function.
            log(
              `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy admin at ${adminAddress}. Expected 5.0.0`,
            );
          }
          const admin = await attachProxyAdminV4(hre, adminAddress, signer);
          return (nextImpl, call) =>
            call
              ? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides)
              : admin.upgrade(proxyAddress, nextImpl, ...overrides);
        }
      }
    }
  }
}

 

코드를 살펴보면 관리자 주소가 비어 있거나 바이트코드가 '0x' 경우,  관리자 컨트랙트가 없는 경우

ITransparentUpgradeableProxy 사용하여 프록시 컨트랙트의 업그레이드 인터페이스 버전을 가져온다.

이후, 인터페이스 버전에 따라 적절한 프록시 컨트랙트를 연결하고, 업그레이더 함수를 반환한다.

 

attachITransparentUpgradeableProxyV5

export async function attachITransparentUpgradeableProxyV5(
  hre: HardhatRuntimeEnvironment,
  address: string,
  signer?: Signer,
): Promise<Contract> {
  return hre.ethers.getContractAt(ITransparentUpgradeableProxyV5.abi, address, signer);
}

attachITransparentUpgradeableProxyV5 함수는 주어진 주소에 있는 프록시 컨트랙트에 연결하고,  컨트랙트의 인스턴스를 반환한다.  인스턴스는 프록시 컨트랙트의 함수를 호출하거나 이벤트를 감지하는  사용할  있다.

 

getContractAt 함수는 컨트랙트의 ABI 주소를 인자로 받아, 해당 주소에 배포된 컨트랙트의 인스턴스를 반환한다.  함수는 주어진 ABI 사용하여 컨트랙트의 함수와 이벤트에 접근할  있게 해준다.

 

getContractAt을 이용하여 프록시 컨트랙트에 연결되었기 때문에 getUpgrader에서 인스턴스를 활용해 

return (nextImpl, call) => proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides);

upgradeToAndCall 함수를 반환한다.

 

다시 upgradeProxy 함수로 돌아가서

const upgradeTo = await getUpgrader(proxyAddress, opts, getSigner(ImplFactory.runner));
    const call = encodeCall(ImplFactory, opts.call);
    const upgradeTx = await upgradeTo(nextImpl, call);

반환된 함수를 호출하여 업그레이드를 진행한다. 

endcodeCall 

function encodeCall(factory: ContractFactory, call: UpgradeProxyOptions['call']): string | undefined {
  if (!call) {
    return undefined;
  }

  if (typeof call === 'string') {
    call = { fn: call };
  }

  return factory.interface.encodeFunctionData(call.fn, call.args ?? []);
}

encodeCall(ImplFactory, opts.call);: 이 부분은 ImplFactory와 opts.call을 사용하여 호출을 인코딩한다.

 ImplFactory는 컨트랙트 팩토리를 참조하고, opts.call은 업그레이드 후에 실행할 함수와 인자를 포함하는 객체이다

 encodeCall 함수는 이 정보를 사용하여 함수 호출을 인코딩한다

 

 const upgradeTx = await upgradeTo(nextImpl, call);: 이 부분은 upgradeTo 함수를 호출하여 프록시 컨트랙트를 업그레이드한다.

 upgradeTo 함수는 getUpgrader 함수에서 반환된 함수를 참조하며, nextImpl과 call을 인자로 받는다. nextImpl은 새로운 구현체의 주소를 참조하고, call은 인코딩된 함수 호출을 참조한다 

upgradeTo 함수는  정보를 사용하여 프록시 컨트랙트를 업그레이드하고, 업그레이드 트랜잭션을 반환한다

  const call = encodeCall(ImplFactory, opts.call);
    const upgradeTx = await upgradeTo(nextImpl, call);

    const inst = attach(ImplFactory, proxyAddress);

const inst = attach(ImplFactory, proxyAddress);: 이 부분은 attach 함수를 호출하여 업그레이드된 프록시 컨트랙트에 연결한다. attach 함수는 ImplFactory와 proxyAddress를 인자로 받는다. ImplFactory는 컨트랙트 팩토리를 참조하고, proxyAddress는 프록시 컨트랙트의 주소를 참조한다. attach 함수는 이 정보를 사용하여 컨트랙트의 인스턴스를 반환한다.

 

inst.deployTransaction = upgradeTx;: 이 부분은 inst 객체의 deployTransaction 속성을 upgradeTx로 설정한다 inst 객체는 업그레이드된 프록시 컨트랙트의 인스턴스를 참조하고, upgradeTx는 업그레이드 트랜잭션을 참조한다. 이 설정을 통해, 사용자는 inst 객체를 통해 업그레이드 트랜잭션에 접근할 수 있다.

 

return inst;:  부분은 inst 객체를 반환한다.  객체는 업그레이드된 프록시 컨트랙트의 인스턴스를 참조하며, 사용자는  객체를 통해 컨트랙트의 함수를 호출하거나 상태를 조회할  있다.

 

다음 장에는 이 방법들의 핵심 함수들만 순서대로 나열해보고 rawTransaction으로 날리는 법을 알아보자

댓글