컨트랙트 24KB 한도는 왜 단순한 불편이 아니라 가스 모델 문제일까: EIP-7907로 보는 코드 크기 과금
스마트컨트랙트 코드 크기 제한을 올릴 때, 왜 “더 큰 파일을 배포하게 해준다”가 아니라 “큰 코드를 읽는 비용을 누가 내는가”부터 봐야 할까?
컨트랙트 24KB 한도는 왜 단순한 불편이 아니라 가스 모델 문제일까: EIP-7907로 보는 코드 크기 과금
- 카테고리: blockchain_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: 스마트컨트랙트 코드 크기 제한을 올릴 때, 왜 “더 큰 파일을 배포하게 해준다”가 아니라 “큰 코드를 읽는 비용을 누가 내는가”부터 봐야 할까?
- 핵심 출처:
- EIP-7907: Meter Contract Code Size And Increase Limit - 생성일 2025-03-14, 확인일 2026-07-14
- ethereum/EIPs PR #9483: Add EIP: Meter Contract Code Size And Increase Limit - 생성 2025-03-14, 병합 2025-03-21, 확인일 2026-07-14
- EIP-170: Contract code size limit - Final, 생성일 2016-11-04, 확인일 2026-07-14
- EIP-3860: Limit and meter initcode - Final, 생성일 2021-07-16, 확인일 2026-07-14
- EIP-2929: Gas cost increases for state access opcodes - Final, 생성일 2020-09-01, 확인일 2026-07-14
1. 왜 지금 봐야 하나
스마트컨트랙트를 조금만 크게 만들다 보면 익숙한 벽을 만난다. Solidity 컴파일 결과가 24KB를 넘으면 배포가 실패하고, 개발자는 라이브러리 분리, proxy, facet, Diamond 패턴, delegatecall, 코드 압축, 에러 문자열 제거 같은 우회로를 찾는다. 이 문제는 보통 “개발자 경험이 나쁘다”로만 이야기되지만, 프로토콜 관점에서는 더 근본적인 질문이다.
컨트랙트 코드를 한 번 호출할 때 클라이언트가 읽고 전처리해야 하는 비용은 누가 지불하는가?
EIP-7907은 이 질문에 대한 최신 제안이다. 이 EIP는 EIP-170이 도입한 runtime contract code size limit을 24KB에서 64KB로 올리고, EIP-3860의 initcode limit도 48KB에서 128KB로 올리자고 제안한다. 동시에 24KB를 초과하는 컨트랙트 코드를 cold 상태에서 로드할 때 초과 코드 32바이트 word당 2 gas를 추가로 부과한다.
중요한 점은 “한도를 없애자”가 아니라는 것이다. EIP-7907은 더 큰 컨트랙트를 허용하되, 큰 코드를 읽는 비용을 gas schedule 안으로 넣어 DoS 위험을 통제하려 한다. 즉 오늘 볼 개념은 컨트랙트 구조 팁이 아니라 프로토콜 API로서의 코드 크기와 가스 과금이다.
2. 핵심 개념
EVM에서 컨트랙트 코드는 그냥 저장된 바이트 배열이 아니다. 어떤 주소를 호출하거나 EXTCODESIZE, EXTCODECOPY 같은 opcode로 코드를 조회하면 클라이언트는 해당 account의 code를 로드해야 한다. 큰 코드는 다음 비용을 만든다.
- 디스크나 DB에서 더 많은 code byte를 읽는다.
- 실행 전
JUMPDEST분석 같은 VM preprocessing이 커진다. - light client나 proof 시스템 관점에서 witness/Merkle proof 크기가 커진다.
- p2p/snap sync 계층에서 큰 객체를 다루는 경계와 만난다.
EIP-170은 2016년 Spurious Dragon에서 이 문제를 하드캡으로 막았다. runtime code가 MAX_CODE_SIZE = 0x6000, 즉 24KB보다 크면 contract creation이 out-of-gas로 실패한다. 당시 rationale은 명확했다. 호출 자체는 일정한 gas처럼 보이지만, 실제 클라이언트는 코드 읽기와 전처리에서 O(n) 비용을 치르므로, 객체 크기 상한이 필요했다.
EIP-3860은 배포 시점의 initcode에도 비슷한 사고를 적용했다. runtime code뿐 아니라 contract creation 중 실행되는 initcode도 길이에 비례해 분석 비용이 있고, 그래서 initcode size를 runtime code limit의 두 배인 48KB로 제한하고 32바이트 chunk당 2 gas를 부과했다.
EIP-7907은 이 흐름을 이어간다.
EIP-170: runtime code 크기를 24KB로 제한한다.
EIP-3860: initcode 크기를 48KB로 제한하고 initcode 길이를 과금한다.
EIP-7907: runtime code 한도를 64KB로 올리되, 24KB 초과분을 cold code load 때 과금한다.
제안의 핵심 상수는 다음처럼 정리할 수 있다.
MAX_CODE_SIZE: 24KB → 64KB
MAX_INITCODE_SIZE: 48KB → 128KB
GAS_PER_CODE_WORD: 2 gas / 32-byte word
초과분 기준: 24KB를 넘는 runtime code 부분
여기서 “cold code load”가 중요하다. EIP-2929 이후 Ethereum gas 모델은 account/storage 접근을 cold/warm으로 나눠, 트랜잭션에서 처음 접근하는 state에는 더 높은 비용을 부과한다. EIP-7907은 contract code에도 유사한 warming 개념을 도입한다. 한 트랜잭션 안에서 처음 큰 코드를 로드할 때는 초과분 비용을 내고, 이미 warm해진 뒤에는 같은 방식으로 반복 과금하지 않는 구조다.
3. 최신 이슈와 연결
EIP-7907의 motivation은 개발자 경험과 프로토콜 안전을 동시에 건드린다. 24KB 제한은 합리적인 보안 장치였지만, 시간이 지나면서 복잡한 DeFi, account abstraction wallet, cross-chain messaging, permissioned module, on-chain verifier 같은 컨트랙트는 한 파일 안에 넣고 싶은 로직이 늘어났다. 개발자는 원래 필요하지 않았던 proxy/facet 구조를 도입하고, 그 결과 audit surface와 upgrade 권한, delegatecall 경계가 늘어난다.
하지만 제한을 단순히 없애면 예전 문제가 돌아온다. 큰 코드를 호출하는 트랜잭션이 실제 클라이언트 리소스를 더 쓰는데 gas가 그대로라면, block gas limit이 올라갈수록 최악의 block 실행 비용이 예측하기 어려워진다. EIP-7907은 그래서 “더 큰 컨트랙트 허용”과 “초과 code load 과금”을 묶는다.
이 관점은 최근 Ethereum scaling 논의와도 연결된다. block gas limit, blob capacity, 병렬 실행, witness size, statelessness 논의는 모두 같은 질문으로 돌아간다.
사용자가 보는 추상 비용(gas)이
클라이언트가 실제로 쓰는 CPU/IO/memory/network 비용을
얼마나 잘 대표하는가?
EIP-7907은 code size를 이 질문 안으로 다시 끌고 들어온다. 큰 컨트랙트가 가능해지는 것은 좋지만, 큰 코드가 여러 call path에서 cold로 로드될 때 비용 모델과 테스트가 바뀐다.
4. 개발자 관점 해석
1) 24KB 한도 우회 패턴은 보안 비용이었다
지금까지 24KB 제한을 넘기 위해 기능을 여러 컨트랙트로 쪼개는 경우가 많았다. 이 자체는 나쁜 설계가 아니다. 문제는 분리가 도메인 경계가 아니라 컴파일 결과를 맞추기 위한 우회가 될 때다. proxy, facet, library, delegatecall은 모두 권한·storage layout·upgrade 경계를 만든다.
EIP-7907식 한도 상향이 활성화된다면 일부 프로젝트는 불필요한 쪼개기를 줄일 수 있다. 그러나 “한 파일에 다 넣어도 된다”가 결론은 아니다. 보안 리뷰는 여전히 module boundary가 필요하다. 달라지는 것은 크기 제한 때문에 생긴 인위적 boundary와 도메인/권한 때문에 필요한 boundary를 분리해서 판단할 수 있다는 점이다.
2) 큰 컨트랙트는 배포 비용보다 호출 비용을 봐야 한다
많은 팀이 contract size를 배포 실패 여부로만 본다. EIP-7907에서는 runtime code가 24KB를 넘는 순간, cold code load 비용이 call path의 일부가 된다. 특히 아래 패턴을 점검해야 한다.
- router가 여러 큰 module을 cold로 처음 호출하는 transaction
- account abstraction wallet이 validation 중 큰 policy/module code를 읽는 경로
- proxy가 EIP-7702 delegation target이나 implementation code를 따라가며 추가 code load를 만드는 경로
- keeper/liquidation/batcher가 한 트랜잭션에서 여러 큰 컨트랙트를 순회하는 경로
개발자에게 필요한 테스트는 “배포가 되나?”가 아니라 “최악의 cold path에서 gas 여유가 얼마나 남나?”다.
3) EIP-7702와 만날 때 code pointer 사고가 필요하다
EIP-7907은 EIP-7702 delegation과 관련된 경우 target account code가 cold라면 추가 gas가 고려되어야 한다고 적는다. 이는 지갑/account abstraction 개발자에게 중요하다. EOA가 코드 포인터를 갖는 세계에서는 사용자가 서명한 동작이 실제로 어떤 target code를 로드하는지, 그 target이 큰 code인지, cold/warm 상태가 validation gas를 어떻게 바꾸는지 살펴야 한다.
즉 “지갑 UX 기능”이 아니라 call graph와 code loading graph를 같이 봐야 한다.
4) audit checklist에 code size budget이 들어가야 한다
컨트랙트 audit에서 흔히 storage layout, access control, reentrancy, oracle, upgrade authority를 본다. EIP-7907 이후에는 code size도 단순 compiler warning이 아니라 운영 risk 항목이 된다.
- 24KB 초과 컨트랙트가 몇 개인가?
- 초과분은 어느 call path에서 처음 로드되는가?
- bundler, relayer, keeper의 gas limit policy가 이를 반영하는가?
- 큰 컨트랙트를 쪼개지 않아 생긴 권한 집중은 없는가?
- 반대로 크기 때문에 쪼갠 proxy/facet이 불필요한 upgrade root authority를 만들지는 않는가?
5) “가스가 오른다”보다 “비용이 더 정직해진다”에 가깝다
EIP-7907은 특정 앱을 벌주려는 제안이 아니다. 클라이언트가 실제로 쓰는 리소스와 gas를 맞추려는 시도다. EIP-2929가 cold account/storage 접근을 더 비싸게 만든 이유도 비슷하다. 비용 모델이 현실 리소스와 어긋나면, 정상 사용자는 싸게 느끼지만 공격자는 최악의 경로를 싸게 반복할 수 있다.
5. 내 프로젝트에 적용할 체크포인트
-
현재 runtime bytecode 크기를 측정한다
- main contract, implementation, facet, module, wallet validator별 deployed bytecode size를 표로 만든다.
- 20KB 이상은 “한도 근처”, 24KB 초과 가능성은 “EIP-7907 이후 과금 대상 후보”로 표시한다.
-
call graph에서 cold code load를 찾는다
- 한 트랜잭션에서 처음 호출되는 큰 컨트랙트가 몇 개인지 본다.
- router → module → hook → oracle → verifier처럼 깊은 경로를 따로 측정한다.
-
우회 구조와 보안 경계를 분리한다
- 이 proxy/facet/library는 도메인 분리 때문에 필요한가, 24KB 때문에 생긴 것인가?
- 24KB 한도가 완화되어도 upgrade 권한과 storage layout 위험을 유지할 이유가 있는가?
-
AA/bundler/relayer gas policy를 점검한다
- validation 단계에서 큰 wallet/module code를 cold로 읽는가?
- user operation simulation과 실제 inclusion 사이에 warm/cold 가정이 달라질 수 있는가?
-
테스트를 warm path만 보지 않는다
- Foundry/Hardhat 테스트에서 같은 컨트랙트를 반복 호출하면 warm path만 보게 될 수 있다.
- “첫 호출”, “새로운 target”, “여러 큰 target 순회” 시나리오를 따로 만든다.
-
문서에 bytecode budget을 남긴다
README나 audit note에 각 컨트랙트의 bytecode size와 설계 이유를 기록한다.- 크기 증가가 보안 경계 변경인지 단순 기능 추가인지 구분한다.
6. 오늘 10분 액션
오늘 바로 할 수 있는 실험은 배포 산출물의 bytecode size 표를 만드는 것이다.
# Foundry 프로젝트라면 예시
forge build --sizes
그다음 아래 표를 채워보자.
Contract / Module Runtime size 왜 분리했나? Cold path 위험
MainRouter __ KB 도메인 경계 / 24KB 우회? 낮음/중간/높음
WalletValidator __ KB 정책 모듈 낮음/중간/높음
BridgeMessenger __ KB 체인별 분리 낮음/중간/높음
OracleAdapter __ KB 외부 의존성 격리 낮음/중간/높음
마지막으로 이 질문을 적어보자.
EIP-7907처럼 64KB 배포가 가능해진다면,
우리 코드는 더 단순해지는가?
아니면 더 큰 단일 권한 덩어리가 되는가?
이 질문에 답하면 “크기 제한을 피하는 개발”에서 “리소스 비용과 권한 경계를 설계하는 개발”로 넘어갈 수 있다.
7. 더 볼 자료
- EIP-7907: 24KB→64KB runtime code limit, 48KB→128KB initcode limit, cold code load 초과분 과금 제안을 확인한다.
- EIP-170: 24KB runtime code limit이 왜 처음 도입되었는지, 코드 로드의 O(n) 비용 rationale을 확인한다.
- EIP-3860: initcode size limit과 initcode word cost가 어떤 문제를 해결했는지 본다.
- EIP-2929: cold/warm state access gas model의 배경을 확인한다.
- ethereum/EIPs PR #9483: EIP-7907이 EIPs repository에 추가된 PR 히스토리를 확인한다.
중복 회피 메모
로컬 content/generated와 Supabase 최근 blockchain_dev 글을 확인했다. 최근 글은 Solana Alpenglow finality, Taiko bridge message-origin verification, OP Stack fault proof 재현성, ERC-4626 vault NAV 조작, P-256/passkey precompile, MODEXP gas repricing, ERC-1271 fail-closed 검증, EIP-7702 delegation, blob fee reserve, ePBS, FOCIL, PeerDAS를 다뤘다. 이번 글은 기존 gas cap·precompile repricing·wallet delegation 글이 아니라 runtime contract code size limit과 cold code loading 과금을 중심으로, 스마트컨트랙트 구조와 클라이언트 리소스 비용이 만나는 지점을 다뤄 중복을 피했다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)