이어서 분석해보자
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으로 날리는 법을 알아보자
댓글