EIP-5656 메모리 복사 명령어 MCOPY (MCOPY - Memory copying instruction)
최종 확정 · EVM·실행 · 원문 상태 Final · 2021-02-01 작성
EVM에 메모리 영역을 복사하는 전용 명령어 MCOPY(0x5E)를 추가합니다. 프리컴파일 호출이나 MLOAD/MSTORE 반복 없이 256바이트 복사를 27가스에 끝낼 수 있습니다.
왜 필요한가
메모리 복사는 스마트컨트랙트에서 아주 기본적인 작업이지만 지금까지 EVM에는 전용 명령어가 없었습니다. 그래서 개발자들은 우회로를 썼습니다. 하나는 입력을 그대로 돌려주는 identity 프리컴파일(precompile)을 CALL로 호출하는 방법인데, 호출 자체에 드는 비용이 커서 비효율적입니다. 다른 하나는 MLOAD와 MSTORE 명령어로 32바이트씩 직접 옮기는 방법인데, 워드당 최소 12가스가 들고 길이가 32바이트로 딱 떨어지지 않으면 마지막 조각을 읽고 마스킹해 다시 저장하는 추가 작업이 필요합니다. 예를 들어 256바이트를 복사하면 identity 프리컴파일로는 최소 157가스(EIP-2929 이후), 펼친 MLOAD/MSTORE로는 최소 96가스가 들지만 MCOPY로는 27가스면 됩니다. 실제 블록 분석(블록 10537502~10538702)에서도 메모리 복사의 약 10.5%가 MCOPY가 있었다면 성능이 나아졌을 것으로 나타났습니다. Solidity와 Vyper 같은 언어가 메모리 복사를 많이 쓰기 때문에 전용 명령어의 효과가 큽니다.
무엇이 바뀌나
지금
- 메모리를 복사하려면 identity 프리컴파일을 CALL로 부르거나 MLOAD와 MSTORE를 반복해야 합니다
- 32바이트로 떨어지지 않는 길이는 마지막 조각을 읽고 마스킹해 다시 저장해야 합니다
- 256바이트 복사에 최소 96가스(펼친 MLOAD/MSTORE) 또는 157가스(identity 프리컴파일)가 듭니다
EIP-5656 이후
- MCOPY 명령어 하나로 임의 길이의 메모리를 복사합니다
- 원본과 대상 구간이 겹쳐도 중간 버퍼를 쓴 것처럼 안전하게 복사됩니다
- 256바이트 복사가 27가스로 줄어듭니다
누구에게 영향이 있나
- 스마트컨트랙트·언어 개발자: Solidity, Vyper, Fe 같은 컴파일러가 메모리 복사, 슬라이스 접근, 메모리 객체 복사를 더 싸게 만들 수 있습니다
- 클라이언트 팀: 새 옵코드를 구현해야 하며 실제로 중간 버퍼를 할당하지 않도록 구현에 주의해야 합니다
- 정적 분석·최적화 도구: CALL과 달리 메모리에만 영향을 준다는 것이 보장되는 명령어라 분석이 쉬워집니다
- 사용자: 메모리 복사를 많이 하는 컨트랙트를 쓸 때 가스 비용이 줄어듭니다
심층 설명
명령어 번호와 스택 입력
MCOPY는 옵코드 0x5E에 배정됩니다. 스택 맨 위부터 dst(복사가 들어갈 메모리 위치), src(원본 위치), length(바이트 길이) 세 값을 꺼내 쓰고 스택에는 아무 값도 돌려주지 않습니다. 이 순서는 CALLDATACOPY, RETURNDATACOPY 같은 기존 복사 명령어와 같게 맞춘 것이라, 컴파일러와 어셈블리 작성자가 기존 복사 명령어와 같은 감각으로 쓸 수 있습니다.
가스 비용 계산
옐로페이퍼 기준으로 MCOPY는 W_copy 그룹에 속하며 그 그룹의 가스 계산을 따릅니다. 원문 작성 시점 기준 공식은 words_copied = (length + 31) // 32, gas_cost = g_verylow + 3 * words_copied + memory_expansion_cost이고 g_verylow는 3입니다. 즉 한 워드(32바이트)를 복사하면 기본 3가스에 복사 비용 3가스를 더해 6가스가 듭니다. length가 0보다 크고 src + length 또는 dst + length가 현재 메모리 길이를 넘으면 메모리가 확장되고 해당 가스가 추가로 부과됩니다. 원문은 최종 기준이 옐로페이퍼의 계산임을 명시합니다.
겹치는 구간도 안전한 복사 의미론
MCOPY는 메모리의 src 위치에서 length바이트를 dst 위치로 복사합니다. 중간 버퍼를 쓴 것처럼 동작하도록 정의되어 원본과 대상 구간이 겹쳐도 올바른 결과가 나옵니다. 원문 테스트 케이스가 이를 보여 줍니다. MCOPY 0 1 8(오프셋 1의 8바이트를 오프셋 0으로)과 MCOPY 1 0 8(오프셋 0의 8바이트를 오프셋 1로)은 모두 겹치는 구간을 다루는데 각각 6가스로 정확한 결과를 냅니다. 같은 위치로 복사하는 MCOPY 0 0 32도 허용됩니다.
기존 복사 방법과의 비용 비교
identity 프리컴파일의 비용은 15 + 3 * (length / 32) 가스에 호출 오버헤드가 더해집니다. CALL 비용이 700으로 오르면서 사실상 쓸모가 없어졌고 EIP-2929가 100으로 낮췄지만 원문은 여전히 합리적 대안이 되기엔 비싸다고 평가합니다. MLOAD/MSTORE 방식은 오프셋을 미리 알고 펼칠 수 있으면 워드당 최소 12가스로 꽤 효율적이지만 실행 시점에 임의 오프셋으로 복사하면 32 ADD로 오프셋을 올리는 비용이 워드당 최소 6가스 더 붙습니다. 마지막 조각이 한 바이트라면 MSTORE8로 효율적으로 저장할 수 있는 특수 사례가 있을 뿐, 부분 워드 처리는 전반적으로 부담이 큽니다. 256바이트 복사 기준으로 EIP-2929 이전 identity 프리컴파일은 최소 757가스, 이후는 최소 157가스, 펼친 MLOAD/MSTORE는 최소 96가스, MCOPY는 27가스입니다.
정적 분석 용이성과 미래 가스 변경 대비
CALL은 일반적으로 어떤 부수효과든 낼 수 있어 정적 분석기와 최적화기가 보수적으로 다뤄야 합니다. 프리컴파일에 특례를 두더라도 미래 하드포크가 CALL의 효과를 바꿀 수 있으므로, identity 프리컴파일을 쓰는 코드의 분석 결과는 특정 블록 구간에서만 유효합니다. 반면 MCOPY는 메모리에만 영향을 준다는 것이 명령어 차원에서 알려져 있습니다. 또 전용 명령어를 두면 앞으로 CALL 계열의 가스 비용이 바뀌어도 메모리 복사 비용이 영향을 받지 않습니다. 원문은 EVM384처럼 계산량이 많은 작업에서 메모리 복사가 큰 오버헤드로 확인되었다는 점도 근거로 듭니다.
하위 호환성과 테스트
MCOPY는 이전에 존재하지 않던 새 명령어입니다. 따라서 이미 배포된 컨트랙트 중 0x5E 바이트를 포함한 코드가 있었다면 이 EIP 이후 동작이 바뀔 수 있다고 원문이 밝힙니다. 구현 검증용 전체 테스트 모음은 ethereum/execution-spec-tests 저장소의 MCOPY 스위트에 있고 원문 본문에도 겹침 복사를 포함한 네 가지 대표 테스트 케이스가 사전·사후 메모리 상태와 가스 사용량(각 6가스)과 함께 실려 있습니다.
보안 고려사항: 실제 중간 버퍼는 금지
명세는 중간 버퍼를 쓴 것처럼 동작한다고 정의하지만 클라이언트 구현이 실제로 중간 버퍼를 할당하면 안 됩니다. 실제 버퍼 할당은 서비스 거부(DoS) 공격의 통로가 될 수 있기 때문입니다. 원문은 C 표준 라이브러리의 memmove 함수가 중간 버퍼 없이 겹침을 처리하는 것을 예로 들고 대부분 언어의 바이트 이동용 내장 함수와 표준 라이브러리는 성능 특성이 올바르다고 설명합니다. 그 밖의 DoS와 메모리 고갈 공격 분석은 메모리를 건드리는 다른 옵코드와 동일한데, 메모리 확장이 같은 가격 규칙을 따르기 때문입니다. 이 EIP의 상태는 Final로 확정되었습니다.