컨트랙트 런타임
Savitri 컨트랙트 런타임은 가스 계량, 스토리지 격리, 이벤트 방출을 갖춘 격리된 환경(샌드박스)에서 스마트 컨트랙트를 실행합니다.
실행 흐름
컨트랙트 호출 TX 도착
│
▼
BaseContract 검증
│ paused, owner, 예약된 슬롯 확인
▼
Gas Meter 초기화
│ TX 수수료에서 gas_limit 결정
▼
ContractStorage 설정
│ 컨트랙트 상태 로드, 캐시 초기화
▼
함수 디스패치
│ function_selector → 핸들러 매핑
▼
실행 (가스 추적 포함)
│ SLOAD, SSTORE, 이벤트, 호출
▼
결과
├── 성공 → 스토리지 커밋, 이벤트 방출
└── 실패 → 모든 변경 사항 롤백
BaseContract
모든 컨트랙트는 BaseContract를 확장합니다. 슬롯 0-99는 예약되어 있습니다:
| 슬롯 | 필드 | 크기 | 설명 |
|---|---|---|---|
| 0 | owner | 32바이트 | 컨트랙트 소유자 주소 |
| 1 | version | u64 | 컨트랙트 버전 |
| 2 | governance_hook | bool | 거버넌스 통합 활성화 |
| 3 | fee_hook | bool | 사용자 정의 수수료 로직 활성화 |
| 4 | paused | bool | 컨트랙트 일시 정지 상태 |
| 5-99 | 예약됨 | - | 향후 사용 |
BaseContract 함수
| 함수 | 접근 | 설명 |
|---|---|---|
owner() | 공개 | 컨트랙트 소유자 조회 |
transfer_ownership(new_owner) | 소유자 전용 | 소유권 이전 |
version() | 공개 | 컨트랙트 버전 조회 |
upgrade(new_version) | 소유자 전용 | 컨트랙트 업그레이드 |
pause() | 소유자 전용 | 모든 작업 일시 정지 |
unpause() | 소유자 전용 | 작업 재개 |
가스 계량
가스 비용
| 연산 | 가스 | 설명 |
|---|---|---|
SLOAD | 100 | 스토리지 읽기 |
SSTORE (새로운) | 20,000 | 스토리지 쓰기 (빈 슬롯) |
SSTORE (업데이트) | 5,000 | 스토리지 쓰기 (기존 슬롯) |
CALL | 2,300 | 크로스 컨트랙트 호출 |
CREATE | 32,000 | 컨트랙트 배포 |
TRANSFER | 300 | 토큰 전송 |
LOG | 375 | 이벤트 방출 (기본) |
LOG_TOPIC | 375 | 추가 토픽당 |
LOG_DATA | 8 | 이벤트 데이터 바이트당 |
CALLDATA | 16 | 호출 데이터 바이트당 |
블록 단위 계산
가스 계량기는 오버헤드를 줄이기 위해 블록 단위 계산 (블록당 100 연산)을 사용합니다. 가스는 개별 연산 단위가 아닌 블록 단위로 계산됩니다.
가스 한도
가스 한도는 트랜잭션 수수료에서 파생됩니다:
gas_limit = tx.fee / gas_price
실행 중 가스가 소진되면 전체 트랜잭션이 롤백됩니다.
컨트랙트 스토리지
슬롯 기반 모델
각 컨트랙트는 키가 u64 슬롯 번호이고 값이 32바이트 배열인 독립적인 키-값 저장소를 가집니다.
슬롯 파생
매핑의 경우, 충돌을 방지하기 위해 Keccak256을 통해 슬롯이 파생됩니다:
// 단순 값
slot = FIXED_SLOT_NUMBER
// 매핑 (address → value)
slot = keccak256(address || base_slot)[0..8] as u64
// 중첩 매핑 (address → address → value)
inner = keccak256(outer_key || base_slot)
slot = keccak256(inner_key || inner)[0..8] as u64
머클 트리
컨트랙트 스토리지는 상태 증명 생성을 위한 머클 트리를 유지합니다.
이벤트 시스템
컨트랙트는 외부 소비자를 위한 이벤트를 방출합니다:
pub struct CustomEvent {
pub event_type: String, // 예: "Transfer", "Approval"
pub topics: Vec<Vec<u8>>, // 인덱싱된 필드
pub data: Vec<u8>, // 인덱싱되지 않은 데이터
}
표준 이벤트 (BaseContract에서):
OwnershipTransferred(old_owner, new_owner)ContractUpgraded(old_version, new_version)Paused(by)Unpaused(by)
컨트랙트 배포
1. DeployTransaction 생성 (to = None, data = 바이트코드)
2. 컨트랙트 주소 할당 (배포자 + 논스에서 파생)
3. BaseContract 초기화 (owner, version 설정)
4. 컨트랙트 생성자 실행 (initialize 함수)
5. CF_CONTRACTS에 ContractInfo 저장
6. ContractDeployed 이벤트 방출
컨트랙트 호출
1. TX 데이터에서 function_selector 파싱
2. CF_CONTRACTS에서 컨트랙트 로드
3. BaseContract 상태 확인 (일시 정지 안됨 등)
4. gas_limit으로 GasMeter 초기화
5. ContractStorage로 함수 실행
6. 성공 시: 스토리지 변경 사항 커밋, 가스 공제
7. 실패 시: 모든 변경 사항 롤백
EVM 인터프리터
evm_interpreter 모듈은 Ethereum 컨트랙트 호환성을 위한 기본 EVM 바이트코드 실행을 제공합니다. 이를 통해 Solidity로 컴파일된 컨트랙트를 Savitri에 배포할 수 있습니다.
메모리 모니터링
memory_monitor는 DoS 공격 방지를 위해 런타임 메모리 사용량을 추적합니다:
- 컨트랙트당 메모리 제한
- 할당 추적
- 한도 초과 시 자동 종료
병렬 실행
parallel 모듈은 독립적인 트랜잭션에 대한 컨트랙트 병렬 실행을 가능하게 합니다:
- 트랜잭션 간 의존성 분석
- 스토리지 슬롯 충돌 감지
- 비충돌 TX의 병렬 실행
- 충돌 TX에 대한 직렬 폴백
컨트랙트 추적
tracing 모듈은 디버깅을 위한 실행 추적을 제공합니다:
- 단계별 실행 로그
- 스토리지 읽기/쓰기 추적
- 연산별 가스 소비
- 이벤트 방출 로그