Polygon과 L2 스마트 컨트랙트의 차이점: 감사인이 발견하는, 프로토콜을 파산하게 만드는 요소들
요약
Polygon과 같은 L2 및 사이드체인 배포 시 EVM 호환성만 믿고 보안 모델의 차이를 간과하면 심각한 자산 유출이 발생할 수 있습니다. 블록 타임스탬프, 시퀀서 동작, 브릿지 신뢰 모델 등 실행 환경의 고유한 특성을 반드시 고려해야 합니다.
핵심 포인트
- EVM 호환성이 동일하다고 해서 보안 모델까지 동일한 것은 아님
- 블록 타임스탬프 및 블록 번호의 정밀도 차이 주의
- 중앙화된 시퀀서에 의한 트랜잭션 순서 조작 위험성
- 브릿지 컨트랙트의 고유한 신뢰 가정 및 장애 모드 이해 필요
2022년 2월, Umbrella Network에서 380만 달러가 유출되었습니다. 공격자가 프로토콜 팀보다 온체인(on-chain) 조건이 어떻게 작동하는지 더 잘 이해하고 있었기 때문입니다. 이 익스플로잇(exploit)은 이례적인 것이 아니었습니다. 그것은 낮은 유동성으로 인해 가능했던 가격 조작 공격(price manipulation attack)이었으며, 이는 L2나 사이드체인(sidechain) 출시 시기에 흔히 나타나는 유동성 환경이었습니다. 팀은 Ethereum 메인넷의 경제 모델에 맞춰 구축했습니다. 하지만 그들은 규칙이 다른 곳에 배포했습니다. 그 격차가 그들에게 모든 것을 앗아갔습니다.
이것이 Polygon, Arbitrum, Optimism, Base 또는 기타 L2/사이드체인에 배포할 때 발생하는 핵심적인 문제입니다. EVM(Ethereum Virtual Machine)은 대체로 동일하기 때문에, 개발자들은 보안 모델도 동일할 것이라고 가정합니다. 하지만 그렇지 않습니다. 그리고 검토 중인 특정 실행 환경을 이해하지 못하는 감사인(auditor)들은 가장 중요한 문제들을 놓치게 될 것입니다.
L2에 배포할 때 실제로 일어나는 일
EVM 호환성은 실재합니다. 당신의 Solidity 코드는 컴파일됩니다. 테스트도 통과합니다. 메인넷 포크(mainnet fork)도 잘 작동합니다. 변하는 것은 그 아래의 모든 것입니다 — 블록 타이밍(block timing), 가스 경제(gas economics), 시퀀서(sequencer) 동작, 브릿징 신뢰 가정(bridging trust assumptions), 그리고 오라클(oracle) 신뢰성입니다. 이것들은 예외적인 사례가 아닙니다. 이것들은 배포 위치에 따라 다르게 작동하는 보안 모델의 핵심 구성 요소입니다.
블록 타임스탬프(block timestamps)를 예로 들어보겠습니다. Ethereum 메인넷에서 채굴자들은 블록 타임스탬프에 대해 약 15초 정도의 조작 여유를 가집니다. Polygon의 PoS 체인에서는 블록 시간이 약 2초이며, 검증인(validator)들이 유사한 상대적 유연성을 가지고 타임스탬프를 제어합니다. 하지만 절대적인 정밀도는 다르며, 시간 민감형 로직(베스팅(vesting), 경매(auctions), TWAP 윈도우)에 block.timestamp를 사용하는 모든 것은 이를 고려해야 합니다. Arbitrum의 경우, 모든 문맥에서 block.number가 기본적으로 Arbitrum 블록 번호를 반환하지 않습니다 — 대신 L1 블록 번호를 반환합니다. 만약 타이밍을 위해 블록 번호를 사용하고 있다면, 수십 배 이상의 오차가 발생할 수 있습니다.
시퀀서 (Sequencer) 자체가 하나의 공격 표면 (Attack surface)입니다. 현재 Arbitrum과 Optimism은 중앙화된 시퀀서를 운영하고 있습니다. 시퀀서는 Ethereum의 멤풀 (Mempool)에서는 불가능한 방식으로 사용자의 트랜잭션을 프런트러닝 (Front-run)하거나, 지연시키거나, 순서를 재배치할 수 있습니다. 이는 트랜잭션 순서가 경제적 이점을 창출하는 모든 프로토콜 — DEX 차익거래 (Arbitrage), 청산 (Liquidations), 가격 곡선이 적용된 NFT 민팅 (NFT mints) — 에 있어 매우 중요합니다. 시퀀서 운영자는 귀하의 위협 모델 (Threat model)이 아마도 고려하지 못했을 특권들을 가지고 있습니다.
그리고 브릿지 (Bridge)가 있습니다. L1에서 L2로 이동하는 모든 자산은 브릿지 컨트랙트를 거칩니다. 모든 브릿지 컨트랙트는 고유의 신뢰 가정 (Trust assumptions), 지연 시간 (Delay windows), 그리고 장애 모드 (Failure modes)를 가집니다. 브릿지된 토큰이나 크로스체인 메시지 (Cross-chain messages)에 의존하는 컨트랙트를 작성할 때, 귀하가 인지하든 못하든 이러한 가정들을 그대로 상속받게 됩니다.
코드의 현실: 실제 환경에서 이것이 깨지는 지점
L2 컨트랙트에서 제가 지속적으로 목격하는 패턴이 하나 있습니다. 메인넷 테스트에서는 문제없이 작동했지만, 더 빠른 체인에서는 악용 가능한 윈도우 (Exploitable windows)를 생성하는 타임스탬프 의존적 로직 (Timestamp-dependent logic)입니다:
취약한 패턴 (Vulnerable Pattern)
// Solidity 0.8.24
// 위험함: Ethereum 메인넷과 유사한 블록 타이밍을 가정함
// Polygon에 배포될 경우 — 2초 단위의 블록이 이 가정을 무너뜨림
contract VestingVault {
uint256 public constant VESTING_PERIOD = 30 days;
mapping(address => uint256) public vestingStart;
mapping(address => uint256) public vestedAmount;
function claimVested() external {
uint256 elapsed = block.timestamp - vestingStart[msg.sender];
uint256 claimable = (vestedAmount[msg.sender] * elapsed) / VESTING_PERIOD;
...
}
}
여기서 더 큰 문제는 단순히 타임스탬프 조작 그 자체만이 아닙니다. 만약 이 컨트랙트가 어떤 이유로든 (예를 들어 USD 표시 한도를 설정하기 위해) AMM 가격 피드 (Price feed)를 참조하고 있다면, Polygon 배포 환경 특유의 낮은 유동성 때문에 동일한 블록 내에서 해당 가격을 조작하는 비용이 매우 저렴해진다는 점이 문제입니다. 이 두 가지를 결합하면 복합적인 공격 벡터 (Compound exploit vector)가 형성되는데, 메인넷은 유동성이 풍부하기 때문에 메인넷 포크 (Mainnet fork) 테스트에서는 아무런 문제가 없는 것처럼 보일 수 있습니다.
수정된 버전 (The Corrected Version)
// Solidity 0.8.24 — OpenZeppelin 5.x 패턴을 사용한 L2 인식 베스팅 (L2-aware vesting)
// Polygon, Arbitrum, Optimism에서 안전하게 작동합니다.
import "@openzeppelin/contracts/utils/math/Math.sol";
contract VestingVaultV2 {
uint256 public constant VESTING_PERIOD = 30 days;
uint256 public constant MAX_TIMESTAMP_DRIFT = 15 seconds; // 명시적 허용 오차 (explicit tolerance)
struct VestingPosition {
uint256 totalAmount;
uint256 claimedAmount;
...
}
하단에 있는 _checkSequencerUp() 패턴에 주목하세요. 이는 Chainlink가 L2 배포(deployment)를 위해 특별히 제공하는 기능입니다. Arbitrum이나 Optimism의 시퀀서(sequencer)가 다운되었다가 다시 가동될 때, 가격 피드(price feeds)가 오래된 데이터(stale)일 수 있습니다. 시퀀서 상태를 확인하지 않는 프로토콜들은 정확히 이러한 시간대에 취약점을 공격(exploited)당해 왔습니다. Chainlink L2 Sequencer Uptime Feed는 이에 대한 직접적인 해결책이며, 제가 감사(audit)를 위해 제출되는 계약들에서 거의 볼 수 없는 부분입니다.
아무도 말하지 않는 Polygon 특유의 문제점들
Polygon PoS는 기술적으로 L2가 아닌 사이드체인(sidechain)입니다. 보안 측면에서 이 차이는 매우 중요합니다. Arbitrum이나 Optimism 같은 Ethereum L2들은 사기 증명(fraud proofs) 또는 유효성 증명(validity proofs)을 통해 상태를 L1에 고정함으로써 Ethereum의 보안성을 상속받습니다. 하지만 Polygon PoS는 그렇지 않습니다. Polygon PoS의 최종성(finality)은 자체 검증인 세트(validator set)에 의해 보호되는데, 이는 약 100개의 검증인으로 구성되어 있으며 경제적 보안성(economic security)이 Ethereum 메인넷보다 현저히 낮습니다. 이는 Polygon을 비하하는 것이 아니라, 여러분의 위협 모델(threat model)을 변화시키는 사실입니다.
실질적으로 이것이 의미하는 바는 다음과 같습니다:
Polygon PoS에 대한 51% 공격은 Ethereum 메인넷에서는 불가능한 방식으로 경제적 타당성을 갖습니다. 실제로 이런 일이 발생했습니다. 2021년 12월 Polygon은 심각한 인플레이션 버그의 타격을 입었는데(화이트햇 공개, 약 $2M의 보상금 지급), 이는 부분적으로 체인 자체의 경제적 이해관계(economic stakes)가 메인넷보다 낮았기 때문입니다.
Polygon에서 Ethereum으로의 브릿지 출금(bridge withdrawals)은 PoS 브릿지의 경우 약 3시간이 소요되며, Plasma 브릿지의 경우 최대 7일까지 걸립니다. 원자적 교차 체인 동작(atomic cross-chain operations)을 가정하는 계약들은 작동이 중단될 것입니다.
가스 토큰(Gas token)은 ETH가 아니라 MATIC(현재 POL)입니다. ETH로 가격을 책정하는 msg.value를 사용하는 모든 계약이 조정 없이 Polygon에 배포된다면, 잘못된 자산으로 가격을 책정하게 됩니다.
마지막 내용은 당연하게 들릴 수도 있습니다. 하지만 실제로 보면 놀라실 겁니다. 저는 직접 목격했습니다.
배포 전 이를 포착하는 방법
L2 또는 Polygon용 계약을 감사할 때, 저는 메인넷(mainnet)과는 다른 체크리스트를 실행합니다. 실제 단계별 과정은 다음과 같습니다:
-
block.timestamp와block.number의 모든 인스턴스를 Grep(검색)합니다. 각 인스턴스에 대해 다음을 질문하십시오: 검증자(validator) 또는 시퀀서(sequencer)가 이를 30초 동안 조작한다면 어떤 취약점(exploit)이 발생하는가? 3분 동안 조작한다면? 2초 단위 블록을 생성하는 Polygon에서는 30초가 15개 블록에 해당하며, 이는 짧은 시간 범위를 다루는 모든 작업에 유의미한 차이를 만듭니다. -
모든 오라클(oracle) 호출을 식별합니다. Chainlink를 사용하나요? 시퀀서 업타임 피드(sequencer uptime feed)를 확인하나요? 메인넷이 아닌, 해당 특정 체인에서 해당 특정 피드의 하트비트(heartbeat)와 편차 임계값(deviation threshold)은 무엇인가요? Polygon의 Chainlink MATIC/USD 피드는 메인넷의 ETH/USD 피드와 다른 파라미터(parameters)를 가집니다. 추측하지 마십시오.
-
모든 브릿지(bridge) 상호작용을 매핑합니다. 토큰은 어디에서 오는가? 만약 브릿지를 통해 넘어온 것이라면, 브릿지가 일시 중단(paused)될 경우 어떤 일이 발생합니까? 메시지가 재전송(replayed)되면 어떻게 됩니까? Nomad 브릿지 해킹 사건($1억 9,000만 달러, 2022년 8월)은 메시지 검증 실패로 인해 발생했습니다. 브릿지를 통해 넘어온 자산의 안전성은 오직 브릿지 계약의 로직만큼만 안전합니다.
-
시퀀서 환경에서의 MEV 노출을 확인합니다. Optimism과 Arbitrum의 중앙화된 시퀀서는 프런트러닝(front-run)을 할 수 있습니다. 만약 프로토콜의 청산 메커니즘이나 더치 경매(dutch auction)가 올바르게 작동하기 위해 경쟁적인 MEV에 의존하고 있다면, 협력적인 시퀀서는 그 경쟁을 제거해 버립니다. 단일 엔티티가 트랜잭션 순서(ordering)를 제어할 때 어떤 일이 발생하는지 모델링하십시오.
-
가스(gas) 가정을 명시적으로 테스트합니다. L2 가스는 저렴하며 메인넷과는 다른 방식으로 가변적입니다. 가스 한도(gas limits)를 보안 메커니즘으로 사용하는 계약(
require(gasleft() > X)등)이나, 특정 작업이 공격할 가치가 없을 정도로 비쌀 것이라고 가정하는 설계는 재평가가 필요합니다. 메인넷 가스비로 500달러가 드는 공격이 Polygon에서는 0.02달러밖에 들지 않을 수 있습니다.
Slither와 같은 정적 도구(Static tools)는 일부 타임스탬프 조작(timestamp manipulation) 패턴을 포착하고 누락된 접근 제어(access controls)를 표시할 수 있습니다. 하지만 이들은 경제적 로직(economic logic)은 포착하지 못합니다. 즉, "이 특정 체인의 유동성이 낮기 때문에 이 오라클(oracle)은 조작 가능하다"와 같은 문제는 정적 스캔(static scan)에서 나타나지 않습니다. 이를 위해서는 배포 환경에 대한 맥락적 분석(contextual analysis)이 필요합니다.
당신을 놀라게 할 사실
L2 스마트 컨트랙트를 감사(auditing)하는 모든 이들은 재진입(reentrancy)과 오버플로(overflow)에 집중합니다. 이는 Solidity 0.8.x 및 OpenZeppelin 5.x 환경에서 이미 해결된 문제입니다. 현재 L2에서 진짜 치명적인 문제는 배포 환경에 대한 가정(deployment environment assumptions)입니다. 메인넷에서 안전했고 메인넷 감사를 통과했던 컨트랙트가, 재감사 없이 블록 타이밍(block timing), 오라클 동작, 경제적 조건이 다른 체인에 배포되는 경우입니다. 컨트랙트는 변하지 않았지만, 보안 모델(security model)이 변한 것입니다.
제가 아는 세 개의 프로토콜이 지난 18개월 동안 정확히 이러한 패턴으로 인해 상당한 손실을 입었습니다. 손실액이 500만 달러 미만이었기에 크립토 트위터(crypto Twitter)의 주요 이슈로 다뤄지지는 않았습니다. 하지만 그렇다고 해서 당신이 네 번째 사례가 되고 싶다는 뜻은 아닐 것입니다.
지금 즉시 취해야 할 구체적인 조치
Arbitrum 또는 Optimism에 배포하는 경우: 가격 오라클(price oracle)을 읽는 모든 함수에 Chainlink L2 Sequencer Uptime Feed 체크를 구현하십시오. 코드는 단 네 줄입니다. 구현하지 않을 핑계는 없습니다.
block.number 사용을 감사하십시오: Arbitrum에서는 일부 컨텍스트에서 block.number가 L1 블록 번호를 반환합니다. 실제 Arbitrum 블록 카운트가 필요한 경우 ArbSys(address(100)).arbBlockNumber()를 사용하십시오.
대상 체인의 실제 블록 시간(block time)을 기준으로 모든 타임락(time-locked) 함수를 재검토하십시오. Ethereum에서 10블록 지연은 약 2분입니다. Polygon에서는 20초입니다. 그것은 타임락이 아니라, 겨우 속도 방지턱 수준에 불과합니다.
컨트랙트를 메인넷이 아닌 실제 대상 체인의 포크(fork)에서 실행하십시오. Foundry에서 사용 중인 L2 RPC를 가리키는 --fork-url을 사용하십시오. 메인넷 포크 테스트는 오라클 가격과 유동성 깊이(liquidity depth)에 대해 당신에게 거짓 정보를 제공할 것입니다.
배포하기 전에: 당신의 컨트랙트를 SmartContractAuditor.ai에 붙여넣으십시오. 이 도구는 L2 특유의 패턴 — 시퀀서 의존성 (sequencer dependency), 타임스탬프 조작 가능 시간대 (timestamp manipulation windows), 오라클 설정 문제 (oracle configuration issues) — 을 식별하고, 각 요소가 당신의 배포 환경에서 왜 위험한지 쉬운 영어로 설명해 줍니다. 배포 후 익스플로잇 (exploit)이 발생한 뒤 30일을 보내는 것보다, 배포 전 30초를 투자하는 것이 훨씬 낫습니다.
체인들은 서로 다릅니다. 컨트랙트도 그 사실을 알아야 합니다. 다른 누군가가 먼저 발견하기 전에 당신의 컨트랙트가 이를 인지하고 있는지 확인하십시오.
추가 읽을거리
거버넌스 공격 벡터 (Governance Attack Vectors): 공격자들이 어떻게 24시간 만에 Beanstalk에서 1억 8,200만 달러를 탈취했는가
반복적으로 발생하는 5가지 스마트 컨트랙트 익스플로잇 (Smart Contract Exploits) 및 감사 (audit)가 이를 계속 놓치는 이유
NFT 스마트 컨트랙트 취약점 (NFT Smart Contract Vulnerabilities): 마켓플레이스 개발자가 출시 전 반드시 감사해야 할 사항
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기