EIP-1153 임시 저장소 명령어 (TLOAD·TSTORE) (Transient storage opcodes)
최종 확정 · EVM·실행 · 원문 상태 Final · 2018-06-15 작성
트랜잭션이 끝나면 자동으로 사라지는 임시 저장소를 EVM에 추가합니다. 새 명령어 TLOAD와 TSTORE로 읽고 쓰며, 디스크를 건드리지 않아 영구 저장소보다 쌉니다.
왜 필요한가
이더리움 트랜잭션 하나는 CALL 같은 호출 명령으로 여러 겹의 실행 프레임을 만들 수 있고 같은 컨트랙트가 한 트랜잭션 안에서 다시 불려 들어올 수도 있습니다. 지금 이 프레임들끼리 정보를 주고받는 길은 호출 입출력과 영구 저장소 두 가지뿐입니다. 중간에 신뢰할 수 없는 다른 컨트랙트가 끼어 있으면 입출력으로 전달하는 방식은 안전하지 않습니다. 대표적인 예가 재진입 잠금(reentrancy lock)입니다. 영구 저장소(SSTORE/SLOAD)를 쓰면 안전하지만 비용이 큽니다. 게다가 런던 하드포크의 EIP-3529가 가스 환불을 트랜잭션이 쓴 가스의 20%로 제한하면서 잠깐 썼다 지우는 슬롯은 환불을 제대로 받기 어려워졌습니다. 재진입 잠금 하나의 환불을 전부 받으려면 다른 작업에 약 8만 가스를 써야 합니다. 이 EIP는 트랜잭션이 끝나면 통째로 버려지는 전용 임시 저장소를 만들어 프레임 사이의 통신 문제를 싸고 단순한 방법으로 풉니다.
무엇이 바뀌나
지금
- 프레임 사이에 값을 전달하려면 비싼 영구 저장소(SSTORE/SLOAD)를 쓰거나, 신뢰할 수 없는 중간 컨트랙트를 거치는 호출 입출력에 기대야 합니다
- 잠깐 썼다 지우는 슬롯도 가스 환불이 트랜잭션 가스의 20%까지만 허용됩니다
- 재진입 잠금 같은 흔한 안전장치가 필요 이상으로 비쌉니다
EIP-1153 이후
- TLOAD와 TSTORE 명령어로 트랜잭션 동안만 유지되는 저장소를 각 100가스에 읽고 씁니다
- 트랜잭션이 끝나면 값이 자동으로 사라지므로 슬롯을 지우는 작업도, 환불 계산도 필요 없습니다
- 재진입 잠금, 한 트랜잭션짜리 ERC-20 승인, 프록시 메타데이터 전달 같은 패턴이 싸고 안전해집니다
누구에게 영향이 있나
- 스마트 컨트랙트 개발자: 재진입 잠금 등 프레임 간 통신 패턴을 훨씬 싼 가스로 구현할 수 있습니다. 다만 값이 호출이 끝나도 사라지지 않고 트랜잭션이 끝나야 사라진다는 수명 차이를 이해하고 써야 합니다.
- 사용자: 재진입 잠금이나 일회성 승인 같은 패턴을 쓰는 앱에서 트랜잭션 가스 비용이 줄어듭니다.
- 클라이언트 팀: 새 명령어 두 개를 구현해야 합니다. 원문은 현재 상태 맵에 변경 기록(journal)과 체크포인트를 더한 구현을 권장합니다.
- 언어·컴파일러 개발자: 주소 방식이 기존 저장소와 같아서 Solidity에 transient 한정자를 더하는 식으로 비교적 쉽게 지원할 수 있습니다.
심층 설명
두 명령어와 주소 방식
TLOAD(0x5c)와 TSTORE(0x5d)가 추가됩니다. 스택 인자는 SLOAD(0x54), SSTORE(0x55)와 같습니다. TLOAD는 스택에서 32바이트 워드 하나를 꺼내 주소로 삼아 임시 저장소의 32바이트 워드를 읽어 스택에 올립니다. TSTORE는 두 워드를 꺼내 위를 주소, 다음을 값으로 삼아 저장합니다. 주소 체계는 SLOAD·SSTORE와 동일하게 32바이트 주소 하나가 고유한 32바이트 워드를 가리킵니다. 초기 드래프트에서는 충돌을 피해 0xb3, 0xb4를 썼지만 충돌이 사라져 현재 값으로 정해졌습니다. 모든 값은 트랜잭션이 끝나면 버려집니다.
가스 비용과 스타이펜드 예외
TSTORE의 가스는 더럽혀진(dirty) 슬롯에 대한 warm SSTORE와 같은 100가스, TLOAD는 이미 읽은(hot) SLOAD와 같은 100가스입니다. 리버트와의 상호작용 때문에 메모리 접근 수준까지 내릴 수는 없다고 원문은 설명합니다. 또 TSTORE는 EIP-2200이 정의한 가스 스타이펜드 검사(남은 가스가 2,300 이하인지 확인)를 적용받지 않습니다. 원래 값을 디스크에서 읽을 필요가 없고 환불도 없어서 가스 계산 규칙이 SSTORE보다 단순합니다.
소유권과 호출 컨텍스트
임시 저장소는 영구 저장소처럼 소유 컨트랙트에게만 보입니다. 소유 컨트랙트의 프레임들은 메모리와 달리 모두 같은 임시 저장소를 공유합니다. DELEGATECALL이나 CALLCODE 안에서는 그 명령을 실행한 쪽(호출자)이 소유자이고 CALL이나 STATICCALL 안에서는 호출 대상(피호출자)이 소유자입니다. 영구 저장소와 같은 규칙입니다. STATICCALL 컨텍스트에서 TSTORE를 실행하면 예외가 발생하고 TLOAD는 허용됩니다.
리버트 동작과 권장 구현
프레임이 리버트되면 그 프레임에 들어온 뒤 일어난 임시 저장소 쓰기가 내부 호출의 쓰기까지 포함해 모두 되돌려집니다. 영구 저장소와 같은 동작입니다. 참조 구현은 현재 상태 맵에 모든 변경을 기록하는 저널(journal)과 체크포인트 목록을 두는 방식입니다. 프레임 진입, 쓰기, 정상 종료는 모두 O(1)이고 리버트만 마지막 체크포인트 이후 저널 항목 수에 비례하는 O(N)입니다. 최악의 경우에도 기존 클라이언트의 상태 저널링과 같은 수준이라 이 경로로 서비스 거부 공격을 만들 수 없다고 원문은 설명합니다.
왜 환불 손질이 아니라 새 명령어인가
SSTORE·SLOAD를 임시 용도에 맞게 더 싸게 손보는 길은 EIP-2200으로 이미 갔지만 EIP-3529가 환불 상한을 20%로 줄이면서 한계가 드러났습니다. 임시 용도만 환불 카운터를 분리하고 상한을 없애는 대안도 있으나, 상한 적용 순서 같은 상호작용 때문에 구현과 이해가 더 복잡합니다. 그래서 환불 카운터와 아예 상호작용하지 않는 별도 장치를 택했습니다. 바이트 단위 메모리형 인터페이스 대신 워드 단위 저장소형을 고른 이유는 매핑 때문입니다. ERC-20 임시 승인처럼 (owner, spender) 같은 임의 키가 필요한데, 선형 메모리로는 매핑 구현이 어렵고 동적 가스와 리버트 처리도 복잡해집니다.
보안 고려사항: 메모리 할당량과 수명
TSTORE는 100가스당 32바이트를 저장하는 선형 비용의 새 메모리 할당 수단입니다. 3,000만 가스면 약 9.15MB까지 쌓을 수 있습니다. MSTORE는 단일 컨텍스트에서 약 3.75MB이지만 컨텍스트당 100만 가스씩 나눠 쓰며 호출로 메모리 확장 비용을 초기화하면 합계 약 20MB까지 가능하므로 기존과 크게 다르지 않습니다. 개발자가 주의할 점도 있는데, 가스를 아끼려고 호출이 끝날 때 슬롯을 지우지 않으면 같은 트랜잭션의 후속 호출이 막히거나 버그가 생길 수 있고 메모리 대용으로 쓰면 호출이 끝나도 값이 남아 재진입 시 예상 밖 동작이 날 수 있습니다. 그 외에는 SSTORE·SLOAD와 똑같이 재진입 위험 등 통상의 주의가 적용됩니다.
호환성과 상태
이 EIP는 하드포크가 있어야 적용됩니다. 기존 명령어의 동작은 바꾸지 않으므로 이미 배포된 스마트 컨트랙트와는 모두 호환됩니다. 상태는 Final로 확정된 명세이며 테스트 모음은 ethereum/execution-spec-tests 저장소의 cancun 디렉터리 아래 eip1153_tstore 경로에 있습니다.