Code is Law의 배신, 그리고 프록시라는 구원 morgan021 2025. 10. 31.
> _
1. 코드 이즈 로(Code is Law)? 한번 배포하면 끝이라는 오해
블록체인, 특히 이더리움 생태계의 등장은 하나의 강력한 이데올로기를 전파했다. 바로 'Code is Law', 코드가 곧 법이라는 선언이다. 이 문장은 중개자 없는 신뢰, 인간의 개입이나 자의적 해석이 배제된 무결점의 자동화된 계약을 상징했다. 한번 이더리움 네트워크에 배포된 스마트 컨트랙트는 그 누구도 수정하거나 중단시킬 수 없다는 '불변성'은 이 새로운 디지털 경제의 대전제였다. 이 불변성은 시스템의 예측 가능성과 신뢰를 담보하는 핵심 기둥으로 여겨졌다.
하지만 이 견고해 보였던 이데올로기는 현실의 냉혹한 벽 앞에서 균열을 보이기 시작했다. 2016년, 전설적인 The DAO 해킹 사건이 터졌다. 코드의 결함, 즉 재진입성 버그를 이용한 공격으로 막대한 양의 이더가 탈취당했다. 코드는 법이었지만, 그 법이 애초에 잘못 쓰여졌던 것이다. 이 사건은 이더리움 커뮤니티를 하드포크라는 극단적인 선택으로 이끌었고, 불변성이라는 신화에 첫 번째 질문을 던졌다.
이후에도 문제는 계속되었다. 2017년, Parity 월렛의 라이브러리 컨트랙트에서 발견된 취약점으로 인해 수억 달러에 달하는 자산이 동결되었다. 이 역시 코드의 결함이었고, 불변성이라는 원칙 때문에 이 동결된 자산을 구제할 방법은 사실상 사라졌다.
이 두 사건은 명백한 진실을 드러냈다. 인간이 작성하는 한 코드는 완벽할 수 없다. 버그는 필연적으로 발생하며, 비즈니스 로직은 시장 상황과 규제에 따라 끊임없이 변화해야 한다. 완벽한 불변성은 신뢰의 상징이 아니라, 스스로를 가두는 감옥이 될 수 있었다. 한번 배포하면 끝이라는 순진한 믿음은, 변화에 대응하지 못하고 도태되는 디지털 유물을 만들 뿐이라는 자각이 생겨났다. 우리는 역설에 직면했다. 시스템의 신뢰를 위해 불변성이 필요했지만, 시스템의 생존을 위해선 '변화', 즉 업그레이드가 필요했다.
2. 진화를 위한 첫걸음: 프록시 패턴의 탄생 배경
불변의 역설. 어떻게 이 모순을 해결할 것인가? 이미 배포된 컨트랙트의 코드를 직접 수정하는 것은 블록체인의 근본 설계상 불가능하다. 그렇다면 해답은 다른 곳에 있어야 했다. 개발자들은 이 문제를 해결하기 위해 영리한 우회로를 고안해냈다. 바로 '프록시 패턴'이다.
프록시 패턴의 핵심 아이디어는 단순하지만 강력하다. 바로 '데이터'와 '로직'을 분리하는 것이다.
전통적인 스마트 컨트랙트는 하나의 컨트랙트 주소 안에 데이터(예: 사용자의 잔고, 토큰의 총 공급량)와 이 데이터를 처리하는 로직(예: 송금 함수, 발행 함수)이 모두 포함되어 있다. 이 때문에 로직에 버그가 발견되어도 데이터를 유지한 채 로직만 교체할 수가 없었다.
프록시 패턴은 이 구조를 이원화한다.
- 프록시 컨트랙트 (Proxy Contract): 이 컨트랙트는 사용자가 실제 상호작용하는 '주소'가 된다. 중요한 것은 이 컨트랙트가 모든 '데이터', 즉 상태(State)를 저장한다는 점이다. 하지만 이곳엔 최소한의 로직만 존재한다.
- 구현 컨트랙트 (Implementation Contract): 이곳에 실제 비즈니스 로직(송금, 민팅 등)이 담겨 있다.
사용자가 프록시 컨트랙트에 트랜잭션을 보내면, 프록시 컨트랙트는 이 요청을 그대로 '구현 컨트랙트'에 전달한다. 이때 사용되는 핵심 기술이 바로 솔리디티의 delegatecall이다. delegatecall은 A 컨트랙트가 B 컨트랙트의 코드를 '가져와서' A 컨트랙트의 맥락(즉, A의 스토리지)에서 실행하는 방식이다.
이 구조의 마법은 '업그레이드' 시점에 발현된다. 만약 로직에 버그가 발견되거나 기능 개선이 필요하면, 개발자는 새로운 로직이 담긴 '구현 컨트랙트 V2'를 배포한다. 그리고 프록시 컨트랙트에게 "이제부터 V1이 아니라 V2의 로직을 참조하라"고 딱 한 번만 알려주면 된다.
결과적으로 사용자는 동일한 주소(프록시 주소)를 계속 사용하면서도, 내부적으로는 새로운 로직이 적용된 서비스를 이용하게 된다. 데이터는 프록시 컨트랙트의 스토리지에 안전하게 보존된 채로 말이다. 이는 마치 은행 금고(데이터)는 그대로 둔 채, 금고 문을 여는 잠금장치(로직)만 교체하는 것과 같다. 이로써 불변의 블록체인 위에서 '진화'할 수 있는 첫걸음이 시작되었다.
3. 투명한 벽 뒤의 진화: 투명 프록시의 작동 원리
프록시 패턴이라는 개념이 정립되자, 이를 구현하는 표준화된 방식이 필요해졌다. 그중 가장 널리 알려지고 초기에 표준으로 자리 잡은 것이 바로 '투명 프록시 패턴 (Transparent Proxy Pattern, TPP)'이다. OpenZeppelin과 같은 라이브러리를 통해 대중화된 이 방식은 '안정성'과 '명확한 권한 분리'에 중점을 둔다.
'투명'이라는 이름이 붙은 이유는 이 프록시가 호출자가 '누구'인지에 따라 투명하게 다르게 작동하기 때문이다. 투명 프록시 패턴은 시스템을 사용하는 주체를 두 부류로 명확히 나눈다.
- 사용자 (User): 일반적인 DApp 사용자. 컨트랙트의 핵심 기능(예: 토큰 전송)을 호출한다.
- 관리자 (Admin): 컨트랙트의 업그레이드를 수행할 수 있는 특별한 권한을 가진 주소.
투명 프록시 컨트랙트 자체에는 정교한 로직이 내장되어 있다. 모든 호출이 프록시에 도달하면, 프록시는 먼저 msg.sender (호출자)가 누구인지 확인한다.
만약 호출자가 '관리자'라면, 프록시는 이 호출을 구현 컨트랙트로 전달하지 않는다. 대신 프록시 컨트랙트 자신이 가진 관리용 함수(예: upgradeTo, changeAdmin 등)를 실행한다.
만약 호출자가 '사용자'라면, 프록시는 이 호출을 delegatecall을 통해 '구현 컨트랙트'로 그대로 전달한다.
이 방식의 가장 큰 장점은 '함수 충돌(Function Clash)'을 원천적으로 방지한다는 것이다. 예를 들어, 구현 컨트랙트에도 관리 목적으로 upgradeTo라는 함수가 있고, 프록시 자체에도 업그레이드를 위한 upgradeTo 함수가 있다고 가정해보자. 만약 관리자가 upgradeTo를 호출했을 때, 이것이 구현 컨트랙트의 함수를 호출한 것인지, 프록시의 업그레이드 함수를 호출한 것인지 모호해질 수 있다. 투명 프록시는 관리자의 호출은 무조건 프록시 자신이 처리하고, 사용자의 호출만 구현부로 넘김으로써 이 혼란을 막는다.
또한, 이 관리자 권한은 보통 'ProxyAdmin'이라는 별도의 컨트랙트에 위임된다. 즉, 프록시 컨트랙트의 관리자는 EOA(개인 지갑)가 아닌 또 다른 컨트랙트 주소가 된다. 이는 관리자 키가 탈취당했을 때의 위험을 줄이고, 향후 거버넌스 시스템(예: DAO)으로 관리 주체를 이전할 때 유연성을 제공한다. 이처럼 투명 프록시는 명확한 역할 분리와 강력한 통제력을 제공하는, 견고하고 체계적인 진화 방식이다.
4. 스스로 진화하는 코드: UUPS, EIP-1822의 혁신
투명 프록시 패턴은 강력했지만, 단점도 명확했다. 바로 '비용'과 '복잡성'이다. 프록시 컨트랙트 자체가 관리자와 사용자를 구분하는 복잡한 라우팅 로직을 담고 있어야 했기에, 배포 비용(가스비)이 비쌌다. 또한 사용자의 모든 호출마다 이 라우팅 로직을 거쳐야 하므로, 트랜잭션 가스비도 미세하게나마 더 소모되었다.
이러한 배경 속에서 더 가볍고 효율적인 대안으로 제시된 것이 'UUPS (Universal Upgradeable Proxy Standard)'이다. 이는 EIP-1822 제안에 기반을 두고 있으며, 투명 프록시와는 정반대의 철학을 가진다.
UUPS의 핵심 철학은 프록시를 '최대한 가볍게' 만드는 것이다. UUPS 프록시 컨트랙트는 EIP-1167(Minimal Proxy) 표준을 따르는 경우가 많다. 이 프록시는 데이터를 저장하는 스토리지와 delegatecall을 수행하는 최소한의 기능 외에는 아무것도 가지지 않는다. 관리자와 사용자를 구분하는 라우팅 로직 따위는 존재하지 않는다.
그렇다면 업그레이드는 어떻게 수행할까? UUPS는 업그레이드 로직을 프록시가 아닌 '구현 컨트랙트' 자체에 포함시킨다.
즉, '구현 컨트랙트 V1' 내부에 upgradeTo와 같은 업그레이드 함수가 존재한다. 관리자가 이 함수를 호출하면, V1의 코드가 실행된다. 이 코드는 delegatecall의 맥락 속에서 실행되므로, 사실상 프록시 컨트랙트의 스토리지를 조작할 권한을 갖는다. V1의 upgradeTo 함수는 프록시의 스토리지에 저장된 '구현 컨트랙트 주소'를 V2의 주소로 변경하도록 명령한다.
이 방식은 코드가 '스스로 진화하는' 형태를 띤다. 프록시는 단지 무대(스토리지)일 뿐이고, 무대 위의 배우(구현 로직)가 스스로 다음 연극(V2)을 무대에 올리는 셈이다.
이러한 설계는 투명 프록시의 단점을 극복한다. 프록시 배포가 매우 저렴해지고, 사용자의 일반적인 트랜잭션 호출 시 가스비 오버헤드가 거의 사라진다. 하지만 이 혁신에는 중대한 책임이 따른다. 만약 개발자가 실수로 '구현 컨트랙트 V2'에 업그레이드 관련 함수를 포함하는 것을 잊어버린다면 어떻게 될까? V2로 업그레이드가 완료되는 순간, 다음 V3로 업그레이드할 수 있는 방법 자체가 사라지게 된다. 진화의 능력을 스스로 거세하는 셈이다. UUPS는 개발자에게 더 높은 수준의 규율과 철저한 검증을 요구하는, 날카롭고 효율적인 진화 방식이다.

5. 진화의 대가: 투명 프록시의 배포 비용과 복잡성
투명 프록시 패턴이 제공하는 안정성과 명확한 권한 분리는 매력적이지만, 세상에 공짜 점심은 없다. 이 견고함에는 분명한 '대가'가 따른다.
가장 큰 대가는 가스 비용이다. 첫째, 배포 비용이다. 투명 프록시 컨트랙트는 단순한 delegatecall 전달자가 아니다. 앞서 언급했듯, 이 컨트랙트는 관리자(Admin)의 호출인지 일반 사용자(User)의 호출인지 판별하는 라우팅 로직을 내장하고 있어야 한다. 이 로직은 프록시의 fallback 함수나 프록시 자체의 함수에 구현되는데, 이는 컨트랙트의 바이트코드를 상당히 증가시킨다. 더 많은 코드는 곧 더 많은 배포 가스비를 의미한다. 또한, 완전한 투명 프록시 시스템을 구축하려면 최소한 3개의 컨트랙트(Proxy, Implementation, ProxyAdmin)를 배포해야 하므로 초기 설정 비용이 높다.
둘째, 트랜잭션 비용이다. 일단 배포된 이후에도 비용은 발생한다. 사용자가 프록시를 통해 구현 컨트랙트의 함수를 호출할 때마다, 프록시는 매번 msg.sender가 관리자인지 확인하는 로직을 실행해야 한다. 이 '관리자 주소 확인' 단계는 SLOAD (스토리지에서 값 읽기) 연산을 포함할 수 있으며, 이는 트랜잭션마다 추가적인 가스 오버헤드를 발생시킨다. 이 비용은 개별 트랜잭션에서는 미미해 보일 수 있지만, 수백만, 수천만 번의 호출이 발생하는 대형 프로토콜에서는 누적되어 상당한 부담이 된다.
비용 문제 외에도 복잡성이 존재한다. 투명 프록시는 함수 충돌을 막기 위해 정교하게 설계되었지만, 이 설계 자체가 또 다른 복잡성을 야기한다. 개발자는 Proxy, ProxyAdmin, Implementation 간의 상호작용과 소유권 관계를 명확히 이해해야 한다. 특히 스토리지 충돌(Storage Collision) 문제는 프록시 패턴 공통의 난제이지만, 투명 프록시에서는 ProxyAdmin의 스토리지 변수까지 고려해야 할 수 있다.
또한, 관리자 호출은 프록시가 직접 처리하고 사용자 호출만 delegatecall로 넘기는 방식은, 관리자가 실수로 '사용자 함수'를 호출하려 할 때(예: 테스트 환경) 혼란을 야기할 수 있다. 관리자는 프록시를 통해서는 구현 컨트랙트의 함수에 접근할 수 없기 때문이다. 이러한 구조적 특성은 시스템을 견고하게 만들지만, 동시에 유연성을 저해하고 개발 및 디버깅 과정을 더 어렵게 만든다.
6. 경량화된 진화: UUPS의 효율성과 장점
투명 프록시가 지불해야 하는 '진화의 대가'에 대한 반작용으로, UUPS는 '효율성'과 '경량화'를 전면에 내세운다. UUPS의 장점은 투명 프록시의 단점을 정확히 뒤집는다.
가장 두드러진 장점은 가스 효율성이다. 첫째, 배포 비용이 획기적으로 저렴하다. UUPS는 EIP-1167(Minimal Proxy)과 결합하는 경우가 많다. EIP-1167 프록시는 구현 컨트랙트의 주소를 하드코딩한 채, 모든 호출을 무조건 delegatecall로 전달하는 매우 작은 크기의 '클론' 컨트랙트다. 이 프록시 자체에는 관리자 판별 로직이나 복잡한 업그레이드 함수가 전혀 포함되지 않는다. 따라서 배포 가스비가 투명 프록시 대비 수십 배 저렴할 수 있다. 이는 특히 사용자가 자신만의 프록시(예: 개별 스마트 월렛)를 생성해야 하는 서비스에서 막대한 비용 절감을 가져온다.
둘째, 트랜잭션 비용이 낮다. 사용자가 프록시를 통해 함수를 호출할 때, 프록시는 어떤 확인 절차도 거치지 않고 즉시 delegatecall을 실행한다. 투명 프록시에서 발생하는 '관리자 주소 확인' 오버헤드가 없다. 이는 사용자가 지불하는 트랜잭션 수수료가 비-프록시 컨트랙트를 직접 호출하는 것과 거의 동일한 수준임을 의미한다.
이러한 가스 효율성 외에도 구조적 단순성이 장점이다. ProxyAdmin과 같은 별도의 관리 컨트랙트가 필요 없다. 업그레이드 로직은 구현 컨트랙트의 일부로 자연스럽게 통합된다. 이는 전체 시스템의 아키텍처를 더 단순하게 만들고, 배포해야 할 컴포넌트의 수를 줄여준다.
또한, UUPS는 유연성을 제공한다. 업그레이드 로직 자체가 구현 컨트랙트 내부에 있으므로, 개발자는 이 로직을 비즈니스 요구에 맞게 커스터마이징하기 용이하다. 예를 들어, 특정 조건 하에서만 업그레이드를 허용하거나, 업그레이드 전에 특정 데이터를 마이그레이션하는 로직을 upgradeTo 함수 내에 직접 구현할 수 있다.
물론, 앞서 언급했듯이 이 모든 효율성과 유연성은 '업그레이드 로직을 다음 버전에 포함시켜야 한다'는 치명적인 전제 조건 하에서만 의미가 있다. 하지만 이 위험을 통제할 수만 있다면, UUPS는 이더리움 메인넷처럼 가스비가 비싼 환경에서 가장 합리적인 진화 방식이 될 수 있다.
7. 어떤 진화를 택할 것인가: 상황별 최적의 업그레이드 전략
'Code is Law'라는 낭만적인 구호는 'Code is Evolvable'이라는 현실적인 패러다임으로 대체되었다. 이제 질문은 '업그레이드를 할 것인가'가 아니라 '어떻게 업그레이드할 것인가'이다. 우리는 투명 프록시라는 견고하지만 무거운 갑옷과, UUPS라는 가볍지만 치명적인 실수를 용납하지 않는 검 사이에서 선택해야 한다.
이 선택은 절대적인 우위의 문제가 아니라, 상황과 트레이드오프의 문제이다.
투명 프록시(TPP)를 선택해야 하는 경우:
- 최고 수준의 안정성이 요구될 때: 프로젝트의 성격이 극도로 보수적이며, 개발팀의 실수를 시스템 차원에서 방지하는 것이 가스비 절약보다 훨씬 더 중요한 경우(예: 수조 원대의 자산을 다루는 핵심 DeFi 프로토콜).
- 관리 로직과 비즈니스 로직의 완벽한 분리가 필요할 때: 업그레이드 권한(관리 로직)을 비즈니스 로직과 완전히 다른 주체(예: ProxyAdmin)가 통제해야 하며, 두 로직이 서로 절대 간섭하지 않아야 한다는 강력한 보안 요구사항이 있을 때.
- 개발팀이 UUPS의 위험을 감수하고 싶지 않을 때: UUPS의 '업그레이드 기능 누락' 리스크가 팀의 개발 문화나 검증 프로세스 하에서 관리 불가능하다고 판단될 때.
UUPS를 선택해야 하는 경우:
- 가스 효율성이 극도로 중요할 때: 이더리움 메인넷이나 L2라도 트랜잭션 비용에 민감한 서비스일 경우. 특히 사용자가 프록시를 '복제(clone)'하여 사용하는 NFT나 스마트 컨트랙트 월렛 프로젝트라면 UUPS는 거의 필수적인 선택이다.
- 빠른 배포와 단순한 아키텍처가 필요할 때: ProxyAdmin과 같은 추가적인 컴포넌트 없이, 더 단순한 구조로 빠르게 프로토타입을 만들고 배포해야 할 때.
- 개발팀이 규율과 자동화된 검증을 갖추고 있을 때: 새로운 구현 컨트랙트가 반드시 업그레이드 인터페이스를 상속받도록 강제하는 내부 개발 표준과 테스트 케이스가 잘 갖춰져 있을 때.
결국, 스마트 컨트랙트의 진화는 기술적 선택을 넘어선 철학적 결정이다. 우리는 불변성이라는 이상과 변화라는 현실 사이에서 프록시라는 다리를 놓았다. 그 다리를 투명한 유리로 견고하게 지을 것인가, 아니면 스스로 형태를 바꾸는 유기적인 금속으로 날렵하게 지을 것인가. 당신의 프로젝트가 추구하는 가치가 무엇인지에 따라, 그 답은 달라질 것이다. 불변의 역설을 넘어선 지금, 진정한 진화의 책임은 개발자의 손에 달려있다.
3줄 요약
[1] 'Code is Law'는 현실의 버그와 변화 요구 앞에 무너졌고, 업그레이드 가능한 '프록시 패턴'이 탄생했다.
[2] '투명 프록시'는 관리 로직을 분리해 안정적이지만 비용이 높고, 'UUPS'는 로직에 업그레이드 기능을 내장해 가볍고 효율적이다.
[3] 둘의 선택은 안정성과 효율성 사이의 트레이드오프이며, 이는 현대 스마트 컨트랙트 설계의 핵심적인 결정이다.
'MACHINE: EXPLOIT' 카테고리의 다른 글
| 블록체인은 어떻게 현실을 움직이는가? 오라클의 양방향 혁명 (0) | 2025.11.07 |
|---|---|
| 블록체인의 심장, EVM은 왜 외부 세계와 단절되어야 하는가 (0) | 2025.11.07 |
| AI '기억 예산'의 숨겨진 규칙. "네"라고 대답했지만, 아무것도 기억하지 않는다 (0) | 2025.11.06 |
| 인터넷이 정보를 해방시켰다면, IBC는 자산을 해방시킨다 (0) | 2025.11.05 |
| EVM은 왜 미래를 예측할 수 없는가 (0) | 2025.11.04 |
| 가장 비싼 땅, 이더리움에서 28바이트를 아끼는 기술 (0) | 2025.10.31 |
| 우아한 포장지의 비밀, transfer는 왜 _transfer를 부를까? (0) | 2025.10.30 |
| 아무도 부르지 않은 함수, fallback이 응답하는 이유 (0) | 2025.10.29 |
| EVM의 DELEGATECALL은 누구의 시점으로 세상을 보는가 (0) | 2025.10.28 |
| 탈중앙화라는 완벽한 환상과 그것을 깨뜨리는 관리자 (0) | 2025.10.26 |