🧠 UUPS vs Transparent Proxy 실전 비교
| 항목 | UUPS | Proxy | Transparent Proxy |
|---|---|---|---|
| 🧱 구조 | Proxy가 매우 단순Logic이 업그레이드 로직 내장 | Proxy가 업그레이드 기능 포함Logic은 기능만 담당 | |
| 🛠 업그레이드 함수 위치 | Logic(Implementation) 계약 안에 있음 | Proxy(AdminProxy) 계약 안에 있음 | |
| 📥 초기화 방식 | initialize() | initialize() | |
| 🧪 사용 난이도 | 약간 더 어려움 (보안 신경 써야 함) | 더 간단하고 자동화됨 | |
| 🔐 보안 | 실수로 Logic에 upgradeTo() 빼먹으면 영원히 못 업그레이드 | Proxy가 모든 업그레이드를 처리하므로 비교적 안전 | |
| 💰 가스비 | ✅ 더 저렴함 (최대 30~40% 차이) | ❌ 더 비쌈 (Proxy가 업그레이드 기능 포함) | |
| 🧩 사용 시점 | 대규모 프로젝트, 가스 절감이 중요한 경우 | 대부분의 일반적인 상황 | |
| 🚀 OpenZeppelin CLI/Plugin 지원 | ✅ 완벽 지원 (kind: "uups") | ✅ 기본 지원 |
⸻
✅ 코드 구조 비교
⸻
🔷 Transparent Proxy (기본 방식)
Logic.sol
contract LaunchPad is Initializable, OwnableUpgradeable {
uint public value;
function initialize(uint _value) public initializer {
__Ownable_init();
value = _value;
}
function setValue(uint _value) external {
value = _value;
}}
배포
const LaunchPad = await ethers.getContractFactory("LaunchPad");
const proxy = await upgrades.deployProxy(LaunchPad, [42], {
initializer: "initialize"
});
업그레이드
const NewVersion = await ethers.getContractFactory("LaunchPadV2");
await upgrades.upgradeProxy(proxy.address, NewVersion);
⸻
🔷 UUPS Proxy (가스 최적화 방식)
Logic.sol
contract LaunchPad is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint public value;
function initialize(uint _value) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();
value = _value;
}
function _authorizeUpgrade(address newImpl) internal override onlyOwner {}}
배포
const LaunchPad = await ethers.getContractFactory("LaunchPad");
const proxy = await upgrades.deployProxy(LaunchPad, [42], {
kind: "uups", // ✅ UUPS 지정
initializer: "initialize"
});
업그레이드
const NewVersion = await ethers.getContractFactory("LaunchPadV2");
await upgrades.upgradeProxy(proxy.address, NewVersion);
⸻
💰 가스비 실전 비교 예시
함수 Transparent Proxy UUPS Proxy
배포 시 200,000 gas 이상 150,000 gas 정도
상태 변경 함수 호출 60,000 gas 45,000 gas
upgradeProxy() 호출 Transparent가 더 간단함 Logic 쪽에 upgradeTo 구현 필수
차이는 클 수 있지만, 극단적일 때 기준입니다.
⸻
🔒 보안 실수 위험 예시 (UUPS)
// 만약 _authorizeUpgrade()를 누락하면?
// contract 업그레이드 불가능 상태로 영구 봉인됨 ❌
UUPS에서는 반드시 function _authorizeUpgrade()를 구현해야 하며,
잘못 구현하거나 안 넣으면 큰 사고로 이어질 수 있습니다.
⸻
🏁 정리: 어떤 걸 쓰면 좋을까?
상황 추천 방식
보안 우선, 복잡하지 않음 ✅ Transparent Proxy
가스 최적화가 중요 (NFT 대량 발행, 여러 컨트랙트 배포 등) ✅ UUPS Proxy
실험/연습/기초 학습 ✅ Transparent (단순함)
여러 컨트랙트를 동일한 로직으로 관리하고 싶다 ✅ Beacon Proxy 고려
⸻
✨ 결론
“처음에는 Transparent로 시작하고, 필요할 때 UUPS로 넘어가라”
→ OpenZeppelin도 이 전략을 기본 권장합니다.
이하는 Hardhat + TypeScript 환경에서 Transparent Proxy vs UUPS Proxy를 직접 배포하고 비교하는 예시입니다.
⸻
🧱 필요한 계약 파일 (요약)
🔹 LaunchPadTransparent.sol
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract LaunchPadTransparent is Initializable, OwnableUpgradeable {
uint public value;
function initialize(uint _value) public initializer {
__Ownable_init();
value = _value;
}
function setValue(uint _value) external {
value = _value;
}}
⸻
🔹 LaunchPadUUPS.sol
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract LaunchPadUUPS is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint public value;
function initialize(uint _value) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();
value = _value;
}
function setValue(uint _value) external {
value = _value;
}
function _authorizeUpgrade(address) internal override onlyOwner {}}
⸻
🚀 실행 방법
1. .sol 파일들을 contracts/ 폴더에 저장
2. 위 main() 스크립트를 scripts/test-uups-vs-transparent.ts로 저장
3. 아래 명령 실행
npx hardhat run scripts/test-uups-vs-transparent.ts --network localhost
'이더리움' 카테고리의 다른 글
| EIP-1559 와 가스요금 계산 (0) | 2025.03.25 |
|---|---|
| ERC-721 의 transferFrom vs. safeTransferFrom (0) | 2025.03.25 |
| 수정 가능한 스마트 컨트랙트 코드 예시 (0) | 2025.03.25 |
| 수정 가능한 스마트 컨트랙트의 배포 방법들 (0) | 2025.03.25 |
| 트랜잭션 오류 발생시, 오류 원인 파악 방법 (0) | 2025.03.20 |