편집 원본: 0x11, Foresight News
편집 원본: 0x11, Foresight News
EVM(Ethereum Virtual Machine)은 256비트 스택 기반의 전 세계적으로 액세스 가능한 튜링 머신입니다. 아키텍처가 다른 가상 머신 및 물리적 머신과 크게 다르기 때문에 EVM에는 도메인별 언어 DSL이 필요합니다(참고: 도메인별 언어는 특정 응용 프로그램 도메인에 초점을 맞춘 컴퓨터 언어를 나타냅니다).
이 기사에서는 Solidity, Vyper, Fe, Huff, Yul 및 ETK의 6개 언어를 소개하면서 EVM DSL 설계의 최신 기술을 살펴봅니다.
언어 버전
Solidity: 0.8.19
Vyper: 0.3.7
Fe: 0.21.0
Huff: 0.3.1
ETK: 0.2.1
Yul: 0.8.19
이 기사를 읽으려면 EVM, 스택 및 프로그래밍에 대한 기본적인 이해가 필요합니다.
Ethereum 가상 머신 개요
EVM은 256비트 스택 기반 튜링 머신입니다. 그러나 컴파일러에 들어가기 전에 몇 가지 기능적 기능을 소개해야 합니다.
EVM은 "튜링 완료"이므로 "정지 문제"가 발생합니다. 요컨대, 프로그램이 실행되기 전에는 프로그램이 나중에 종료할지 여부를 결정할 방법이 없습니다. EVM이 이 문제를 해결하는 방법은 일반적으로 명령을 실행하는 데 필요한 물리적 리소스에 비례하는 "가스" 계산 단위를 사용하는 것입니다. 각 트랜잭션의 Gas 양은 제한되어 있으며 트랜잭션 개시자는 트랜잭션에 의해 소비된 Gas에 비례하여 ETH를 지불해야 합니다. 이 전략의 의미 중 하나는 기능적으로 동일한 두 개의 스마트 계약이 있는 경우 가스를 덜 소비하는 것이 더 많이 채택된다는 것입니다. 그 결과 특정 작업을 위해 가스 소비를 최소화하기 위해 노력하는 엔지니어와 함께 극한의 가스 효율성을 위해 프로토콜이 경쟁하게 됩니다.
또한 계약이 호출되면 실행 컨텍스트가 생성됩니다. 이 컨텍스트에서 계약에는 작업 및 처리를 위한 스택, 읽기 및 쓰기를 위한 선형 메모리 인스턴스, 계약 읽기 및 쓰기를 위한 로컬 영구 저장소, 호출에 연결된 데이터 "calldata"는 읽을 수 있지만 기록할 수는 없습니다. .
메모리에 대한 중요한 참고 사항은 크기에 명확한 "상한선"이 없지만 여전히 유한하다는 것입니다. 메모리 확장의 가스 비용은 동적입니다. 임계값에 도달하면 메모리 확장 비용은 2차적으로 증가합니다. 즉, 가스 비용은 추가 메모리 할당의 제곱에 비례합니다.
계약은 또한 몇 가지 다른 명령을 사용하여 다른 계약을 호출할 수 있습니다. "호출" 명령은 데이터와 선택적으로 ETH를 대상 계약으로 보낸 다음 대상 계약의 실행이 중지될 때까지 자체 실행 컨텍스트를 생성합니다. "staticcall" 지시문은 "call"과 동일하지만 정적 호출이 완료될 때까지 전역 상태의 어떤 부분도 업데이트되지 않았음을 확인하는 검사를 추가합니다. 마지막으로 "delegatecall" 지시문은 이전 컨텍스트의 일부 환경 정보를 유지한다는 점을 제외하면 "call"처럼 동작합니다. 이것은 일반적으로 외부 라이브러리 및 프록시 계약에 사용됩니다.
언어 설계가 중요한 이유
비정형 아키텍처와 상호 작용할 때 도메인 특정 언어(DSL)가 필요합니다. LLVM과 같은 컴파일러 도구 체인이 존재하지만 스마트 계약을 처리하기 위해 이들에 의존하는 것은 프로그램 정확성과 계산 효율성이 중요한 경우 이상적이지 않습니다.
스마트 계약은 기본적으로 변경 불가능하고 블록체인 가상 머신(VM)의 속성을 고려할 때 스마트 계약은 금융 애플리케이션에서 널리 선택되기 때문에 절차상의 정확성이 중요합니다. EVM에 대한 업그레이드 가능한 솔루션이 존재하지만 기껏해야 패치이고 최악의 경우 임의 코드 실행 취약점입니다.
계산 효율성도 중요합니다. 계산을 최소화하면 경제적 이점이 있지만 보안이 희생되지는 않기 때문입니다.
요컨대, EVM DSL은 프로그램 정확성과 가스 효율성의 균형을 유지해야 하며, 너무 많은 유연성을 희생하지 않으면서 서로 다른 트레이드오프를 만들어 그 중 하나를 달성해야 합니다.
언어 개요
각 언어에 대한 주요 기능과 디자인 선택을 설명하고 간단한 계산 기능 스마트 계약을 포함합니다. 단어 인기도는 Defi Llama의 총 가치 잠금(TVL) 데이터를 기반으로 결정됩니다.
Solidity
Solidity는 C, Java 및 Javascript와 유사한 구문을 사용하는 고급 언어입니다. TVL에서 가장 많이 사용하는 언어로 TVL이 차순위 언어의 10배입니다. 코드 재사용을 위해 다중 상속을 활용하여 스마트 계약을 클래스 객체로 취급하는 객체 지향 패턴을 사용합니다. 컴파일러는 C++로 작성되었으며 향후 Rust로 마이그레이션할 계획입니다.
변경 가능한 계약 필드는 해당 값이 컴파일 시간(상수) 또는 배포 시간(불변)에 알려지지 않는 한 영구 저장소에 저장됩니다. 컨트랙트에 선언된 메서드는 기본적으로 pure, view, payable 또는 non-payable로 선언할 수 있지만 상태는 수정할 수 있습니다. 순수 메서드는 실행 환경에서 읽지 않으며 영구 저장소를 읽거나 쓸 수도 없습니다. 뷰 메서드는 영구 저장소 또는 실행 환경에서 데이터를 읽을 수 있지만 영구 저장소에 쓸 수 없으며 트랜잭션 로그 추가와 같은 부작용을 만들 수도 없습니다. 지불 가능한 방법은 영구 저장소를 읽고 쓸 수 있고, 실행 환경에서 데이터를 읽고, 부작용을 일으키고, 호출에 첨부된 ETH를 받을 수 있습니다. non-payable 메소드는 payable 메소드와 동일하지만 현재 실행 컨텍스트에 첨부된 ETH가 없음을 확인하는 런타임 검사가 있습니다.
참고: 트랜잭션에 ETH를 첨부하는 것은 가스 수수료 지불과 별개입니다. 첨부된 ETH는 컨트랙트에서 수신하며, 컨텍스트 복원을 통해 수락하거나 거부할 수 있습니다.
계약 범위 내에서 선언된 경우 메서드는 private, internal, public 또는 external의 네 가지 가시성 한정자 중 하나를 지정할 수 있습니다. 비공개 메서드는 현재 계약 내에서 "점프" 명령을 통해 내부적으로 액세스할 수 있습니다. 상속된 계약은 비공개 메서드에 직접 액세스할 수 없습니다. 내부 메서드는 "점프" 명령을 통해 내부적으로 액세스할 수도 있지만 상속된 계약은 내부 메서드를 직접 사용할 수 있습니다. 공용 메서드는 새 실행 컨텍스트를 생성하는 "호출" 명령을 통해 외부 계약에서 액세스할 수 있으며 내부적으로는 메서드를 직접 호출할 때 점프를 통해 액세스할 수 있습니다. 공용 메서드는 메서드 호출 앞에 "this."를 붙여서 새 실행 컨텍스트의 동일한 계약 내에서 액세스할 수도 있습니다. 외부 메소드는 "호출" 명령을 통해서만 액세스할 수 있으며, 다른 계약에서든 동일한 계약에서든 메소드 호출 전에 "this."를 추가해야 합니다.
참고: "점프" 명령은 프로그램 카운터를 조작하고 "호출" 명령은 대상 계약이 실행되는 동안 새 실행 컨텍스트를 만듭니다. 가능한 경우 "콜" 대신 "점프"를 사용하는 것이 가스 효율이 더 좋습니다.
Solidity는 또한 라이브러리를 정의하는 세 가지 방법을 제공합니다. 첫 번째는 외부 라이브러리로, 체인에 별도로 배포되고 계약이 호출될 때 동적으로 연결되며 "delegatecall" 명령을 통해 액세스되는 상태 비저장 계약입니다. 이는 외부 라이브러리에 대한 열악한 도구 지원, "대리인 호출" 비용이 많이 들고 영구 저장소에서 추가 코드를 로드해야 하며 배포하는 데 여러 트랜잭션이 필요하기 때문에 가장 일반적이지 않은 접근 방식입니다. 내부 라이브러리는 각 메서드가 내부 메서드로 정의되어야 한다는 점을 제외하면 외부 라이브러리와 동일한 방식으로 정의됩니다. 컴파일 시간에 내부 라이브러리가 최종 계약에 포함되고 데드 코드 분석 단계에서 라이브러리에서 사용되지 않는 메서드가 제거됩니다. 세 번째 방법은 내부 라이브러리와 유사하지만 라이브러리 내부에 데이터 구조와 기능을 정의하는 대신 파일 수준에서 정의하고 최종 계약에서 직접 가져와서 사용할 수 있습니다. 세 번째 방법은 더 나은 인간-컴퓨터 상호 작용을 제공하며, 사용자 지정 데이터 구조를 사용하고, 전역 범위에서 함수를 적용하고, 일부 함수에 별칭 연산자를 어느 정도 적용할 수 있습니다.
컴파일러는 두 가지 최적화 단계를 제공합니다. 첫 번째는 최종 바이트코드에서 최적화 작업을 수행하는 명령 수준 최적화 프로그램입니다. 두 번째는 최근 컴파일 과정에서 IR(Intermediate Representation)으로 Yul 언어(자세한 내용은 후술함)의 사용이 증가한 다음 생성된 Yul 코드를 최적화하는 것입니다.
계약에서 공개 및 외부 메서드와 상호 작용하기 위해 Solidity는 해당 계약과 상호 작용하기 위한 ABI(Application Binary Interface) 표준을 지정합니다. 현재 Solidity ABI는 EVM DSL의 사실상의 표준으로 간주됩니다. 외부 인터페이스를 지정하는 Ethereum ERC 표준은 Solidity의 ABI 사양 및 스타일 가이드에 따라 구현됩니다. 다른 언어도 거의 편차 없이 Solidity의 ABI 사양을 따릅니다.
Solidity는 EVM 명령어 세트에 대한 저수준 액세스를 허용하는 인라인 Yul 블록도 제공합니다. Yul 블록에는 Yul 기능의 하위 집합이 포함되어 있습니다. 자세한 내용은 Yul 섹션을 참조하세요. 이는 종종 가스 최적화, 상위 수준 구문에서 지원하지 않는 기능을 활용하고 스토리지, 메모리 및 호출 데이터를 사용자 지정하는 데 사용됩니다.
Solidity의 인기로 인해 개발자 도구는 매우 성숙하고 잘 설계되었으며 Foundry는 이와 관련하여 탁월한 대표자입니다.
다음은 Solidity로 작성된 간단한 계약입니다.

Vyper
Vyper는 Python과 유사한 구문을 가진 고급 언어입니다. 약간의 차이점이 있는 Python의 하위 집합입니다. 두 번째로 인기 있는 EVM DSL입니다. Vyper는 보안, 가독성, 감사 가능성 및 가스 효율성에 최적화되어 있습니다. 객체 지향 패턴, 인라인 어셈블리를 사용하지 않으며 코드 재사용을 지원하지 않습니다. 컴파일러는 Python으로 작성되었습니다.
영구 저장소에 저장된 변수는 파일 수준에서 선언됩니다. 컴파일 타임에 값을 알면 "상수"로 선언할 수 있고 배포 시 값을 알면 "불변"으로 선언할 수 있으며 공개로 표시하면 최종 계약에서 읽기를 노출합니다. 해당 변수에 대해서만 기능합니다. 상수 및 불변 값은 이름으로 내부적으로 액세스하지만 영구 저장소의 가변 변수는 이름 앞에 "self."를 붙여 액세스할 수 있습니다. 이는 저장된 변수, 함수 매개변수 및 로컬 변수 간의 네임스페이스 충돌을 방지하는 데 유용합니다.
Solidity와 마찬가지로 Vyper는 기능 속성을 사용하여 기능의 가시성과 가변성을 나타냅니다. "@external"로 표시된 기능은 "호출" 명령을 통해 외부 계약에서 액세스할 수 있습니다. "@internal"로 표시된 기능은 동일한 계약 내에서만 액세스할 수 있으며 접두사로 "self."를 붙여야 합니다. "@pure"로 표시된 함수는 실행 환경이나 영구 저장소에서 읽을 수 없으며 영구 저장소에 쓸 수도 없고 부작용을 일으킬 수도 없습니다. "@view"로 표시된 함수는 실행 환경 또는 영구 저장소에서 데이터를 읽을 수 있지만 영구 저장소에 쓰거나 부작용을 일으킬 수는 없습니다. "@payable"로 표시된 기능은 영구 저장소를 읽거나 쓸 수 있고, 부작용을 일으키고, ETH를 수락하거나 받을 수 있습니다. 이 가변성 속성을 선언하지 않은 함수는 기본적으로 지불 불가능으로 설정됩니다. 즉, 지불 가능 기능처럼 작동하지만 ETH를 받을 수 없습니다.
Vyper 컴파일러는 또한 로컬 변수를 스택이 아닌 메모리에 저장하도록 선택합니다. 이것은 계약을 더 간단하고 효율적으로 만들고 다른 고급 언어에서 흔히 발생하는 "너무 깊은 스택" 문제를 해결합니다. 그러나 이것은 또한 약간의 장단점이 있습니다.
또한 메모리 레이아웃은 컴파일 타임에 알려야 하므로 동적 유형의 최대 용량도 컴파일 타임에 알려야 하는 한계가 있습니다. 또한 많은 양의 메모리를 할당하면 EVM 개요 섹션에서 언급한 것처럼 비선형 가스 소비로 이어질 수 있습니다. 그러나 많은 사용 사례에서 이 가스 비용은 무시할 수 있습니다.
Vyper는 인라인 어셈블리를 지원하지 않지만 Solidity 및 Yul의 거의 모든 기능을 Vyper에서도 구현할 수 있도록 더 많은 내장 기능을 제공합니다. 저수준 비트 작업, 외부 호출 및 프록시 계약 작업은 내장 함수를 통해 액세스할 수 있으며 컴파일 타임에 오버레이 파일을 제공하여 사용자 지정 스토리지 레이아웃을 구현할 수 있습니다.
Vyper에는 풍부한 개발 도구 모음이 없지만 보다 긴밀하게 통합된 도구가 있으며 Solidity 개발 도구에 연결할 수도 있습니다. 주목할만한 Vyper 도구에는 실험 및 개발을 위한 EVM 및 Vyper와 관련된 많은 내장 도구가 있는 Titanaboa 인터프리터와 컴파일 타임 코드 실행 기능이 있는 Vyper 기반 Lisp인 Dasy가 있습니다.
다음은 Vyper로 작성된 간단한 계약입니다.

Fe
Fe는 현재 대부분의 기능을 사용할 수 없는 높은 수준의 Rust 유사 언어로 현재 활발히 개발 중입니다. 컴파일러는 주로 Rust로 작성되지만 C++로 작성된 Yul 옵티마이저에 의존하여 중간 표현(IR)으로 Yul을 사용합니다. 이것은 Rust 네이티브 백엔드인 Sonatina의 추가로 변경될 것으로 예상됩니다. Fe는 코드 공유를 위해 모듈을 사용하므로 객체 지향 패턴을 사용하는 대신 Rust와 같은 방식으로 가져올 수 있는 모듈 내에서 변수, 유형 및 함수가 선언되는 모듈 기반 시스템을 통해 코드를 재사용합니다.
영구 저장소 변수는 계약 수준에서 선언되며 수동으로 정의된 getter 함수 없이는 공개적으로 액세스할 수 없습니다. 상수는 파일 또는 모듈 수준에서 선언할 수 있으며 계약 내에서 액세스할 수 있습니다. 변경할 수 없는 배포 시간 변수는 현재 지원되지 않습니다.
메서드는 모듈 수준 또는 계약 내에서 선언할 수 있으며 기본값은 순수하고 비공개입니다. 계약 메서드를 공개하려면 정의 앞에 "pub" 키워드가 있어야 외부에서 액세스할 수 있습니다. 영구 저장 변수에서 읽으려면 메서드의 첫 번째 매개 변수는 "self"여야 하며 변수 이름 앞에 "self"를 붙입니다. 메서드는 로컬 저장 변수에 대한 읽기 전용 액세스를 제공합니다. 영구 저장소를 읽고 쓰려면 첫 번째 인수가 "mut self"여야 합니다. "mut" 키워드는 계약의 저장소가 메서드 실행 중에 변경 가능함을 나타냅니다. 환경 변수에 액세스하려면 일반적으로 "ctx"라는 이름의 메서드에 "컨텍스트" 매개변수를 전달하면 됩니다.
함수 및 사용자 정의 유형은 모듈 수준에서 선언할 수 있습니다. 기본적으로 모듈 항목은 비공개이며 "pub" 키워드를 추가하지 않으면 액세스할 수 없습니다. 그러나 계약 수준에서 "pub" 키워드와 혼동하지 마십시오. 모듈의 공개 멤버는 최종 계약 또는 다른 모듈 내에서만 액세스할 수 있습니다.
Fe는 현재 인라인 어셈블리를 지원하지 않습니다. 대신 명령어는 컴파일 시간에 명령어로 확인되는 컴파일러 내장 함수 또는 특수 함수로 래핑됩니다.
Fe는 유형 별칭, 하위 유형이 있는 열거형, 특성 및 제네릭을 지원하는 Rust의 구문 및 유형 시스템을 따릅니다. 이에 대한 지원은 현재 제한적이지만 진행 중입니다. 다양한 유형에 대해 특성을 정의하고 구현할 수 있지만 제네릭이나 특성 제약 조건은 모두 지원되지 않습니다. 열거형은 하위 유형 지정을 지원하고 열거형에 메서드를 구현할 수 있지만 외부 함수에서 코딩할 수는 없습니다. Fe의 유형 시스템은 여전히 진행 중인 작업이지만 개발자가 더 안전한 컴파일 시간 검사 코드를 작성할 수 있는 많은 가능성을 보여줍니다.
다음은 Fe로 작성된 간단한 계약입니다.

Huff
Huff는 수동 스택 제어 및 EVM 명령어 세트의 추상화를 최소화하는 어셈블리 언어입니다. "#include" 지시문을 통해 포함된 모든 Huff 파일을 컴파일 시간에 구문 분석하여 코드 재사용을 달성할 수 있습니다. 극도로 최적화된 타원 곡선 알고리즘을 위해 원래 Aztec 팀에서 작성한 이 컴파일러는 나중에 TypeScript와 Rust로 다시 작성되었습니다.
상수는 컴파일 타임에 정의되어야 하며, 불변은 현재 지원되지 않으며 영구 저장 변수는 언어에서 명시적으로 정의되지 않습니다. 명명된 저장소 변수는 높은 수준의 추상화이므로 Huff의 영구 저장소에 대한 쓰기는 쓰기를 위한 opcode "sstore"와 읽기를 위한 "sload"를 통해 수행됩니다. 사용자 지정 스토리지 레이아웃은 사용자 정의하거나 규칙에 따라 0에서 시작하여 컴파일러의 고유 "FREE_STORAGE_POINTER"를 사용하여 각 변수를 증가시킬 수 있습니다. 저장된 변수를 외부에서 액세스 가능하게 만들려면 변수를 읽고 호출자에게 반환할 수 있는 코드 경로를 수동으로 정의해야 합니다.
외부 함수도 고급 언어에서 도입된 추상화이므로 Huff에는 외부 함수 개념이 없습니다. 그러나 대부분의 프로젝트는 다른 고급 언어, 가장 일반적으로 Solidity의 ABI 사양을 다양한 정도로 따릅니다. 일반적인 패턴은 원시 호출 데이터를 로드하고 이를 사용하여 함수 선택기와 일치하는지 확인하는 "스케줄러"를 정의하는 것입니다. 일치하면 후속 코드가 실행됩니다. 스케줄러는 사용자 정의이므로 서로 다른 스케줄링 패턴을 따를 수 있습니다. Solidity는 스케줄러의 셀렉터를 이름에 따라 알파벳순으로 정렬하고, Vyper는 숫자순으로 정렬하고 런타임 시 이진 검색을 수행하며, 대부분의 Huff 스케줄러는 점프 테이블을 거의 사용하지 않고 예상 함수 사용 빈도별로 정렬합니다. 현재 점프 테이블은 EVM에서 기본적으로 지원되지 않으므로 이를 구현하려면 "코드 복사"와 같은 인트로스펙션 명령이 필요합니다.
내장 함수는 "#define fn" 지시문을 사용하여 정의되며, 유연성을 위해 템플릿 매개변수를 허용하고 함수의 시작과 끝에서 예상되는 스택 깊이를 지정할 수 있습니다. 이러한 기능은 내부 기능이므로 외부에서 액세스할 수 없으며 내부 액세스에는 "점프" 명령을 사용해야 합니다.
조건문 및 루프문과 같은 기타 제어 흐름은 점프 대상을 사용하여 정의할 수 있습니다. 점프 대상은 콜론 뒤에 오는 식별자로 정의됩니다. 식별자를 스택에 푸시하고 점프 명령을 실행하여 이러한 대상으로 점프할 수 있습니다. 이는 컴파일 타임에 바이트코드 오프셋으로 해석됩니다.
매크로는 "#define macro"로 정의되며, 그렇지 않으면 내부 함수와 동일합니다. 주요 차이점은 매크로가 컴파일 시간에 "점프" 명령을 생성하지 않고 대신 매크로 본문을 파일의 각 호출에 직접 복사한다는 것입니다.
이 디자인은 여러 번 호출될 때 코드 크기가 증가하는 대가로 임의 점프 감소와 런타임 가스 비용 사이의 관계에 무게를 둡니다. "MAIN" 매크로는 계약의 진입점으로 간주되며 본문의 첫 번째 명령은 런타임 바이트코드의 첫 번째 명령이 됩니다.
컴파일러에 내장된 다른 기능으로는 로깅을 위한 이벤트 해시 생성, 발송을 위한 함수 선택기, 오류 처리를 위한 오류 선택기, 내장 함수 및 매크로를 위한 코드 크기 검사기 등이 있습니다.
참고: "// [count]"와 같은 스택 주석은 필요하지 않으며 라인 실행이 끝날 때 스택 상태를 나타내는 데만 사용됩니다.
다음은 Huff로 작성된 간단한 계약서입니다.

ETK
ETK(EVM Toolkit)는 수동 스택 관리 및 최소한의 추상화가 포함된 어셈블리 언어입니다. 코드는 "%include" 및 "%import" 지시문을 통해 재사용할 수 있으며 컴파일러는 Rust로 작성됩니다.
Huff와 ETK의 주목할만한 차이점 중 하나는 Huff가 특별한 "CONSTRUCTOR" 매크로를 정의하여 재정의할 수 있는 생성자 코드라고도 하는 initcode에 약간의 추상화를 추가한다는 것입니다. ETK에서 이것들은 추상화되지 않고 initcode와 런타임 코드는 함께 정의되어야 합니다.
Huff와 마찬가지로 ETK는 "sload" 및 "sstore" 명령을 통해 영구 저장소를 읽고 씁니다. 그러나 상수 또는 변경 불가능한 키워드는 없지만 ETK의 두 가지 유형의 매크로 중 하나인 식 매크로를 사용하여 상수를 에뮬레이트할 수 있습니다. 식 매크로는 지시문으로 구문 분석되지 않지만 대신 다른 지시문에서 사용할 수 있는 숫자 값을 생성합니다. 예를 들어 "push" 명령을 정확하게 생성하지 않을 수 있지만 "push" 명령에 포함할 숫자를 생성할 수 있습니다.
앞에서 언급했듯이 외부 함수는 고급 언어 개념이므로 코드 경로를 외부에 노출하려면 함수 선택기 디스패처를 만들어야 합니다.
내장 함수는 다른 언어와 같이 명시적으로 정의되지 않습니다. 대신 점프 대상에 대한 사용자 정의 별칭을 지정하고 해당 이름으로 점프할 수 있습니다. 이는 또한 루프 및 조건문과 같은 다른 제어 흐름을 허용합니다.
ETK는 두 종류의 매크로를 지원합니다. 첫 번째는 임의 개수의 인수를 허용하고 다른 명령어에서 사용할 수 있는 숫자 값을 반환할 수 있는 식 매크로입니다. 표현식 매크로는 명령을 생성하지 않고 즉각적인 값이나 상수를 생성합니다. 그러나 지시문 매크로는 임의 개수의 인수를 허용하고 컴파일 시간에 임의 개수의 지시문을 생성합니다. ETK의 명령 매크로는 Huff 매크로와 유사합니다.
다음은 ETK로 작성된 간단한 계약서입니다.

Yul
Yul은 높은 수준의 제어 흐름과 추상화가 많은 어셈블리 언어입니다. Solidity 도구 체인의 일부이며 선택적으로 Solidity 빌드 파이프라인에서 사용할 수 있습니다. Yul은 독립 실행형 언어가 아닌 컴파일 대상이 되기 때문에 코드 재사용을 지원하지 않습니다. 컴파일러는 C++로 작성되었으며 나머지 Solidity 파이프라인과 함께 Rust로 마이그레이션할 계획입니다.
율에서 코드는 코드, 데이터 및 중첩된 개체를 포함할 수 있는 개체로 나뉩니다. 따라서 Yul에는 상수나 외부 함수가 없습니다. 외부 세계에 코드 경로를 노출하려면 함수 선택자 디스패처를 정의해야 합니다.
스택 및 제어 흐름 명령을 제외하고 대부분의 명령은 Yul에서 함수로 노출됩니다. 코드 크기를 줄이기 위해 지시문을 중첩할 수 있으며 임시 변수에 할당한 다음 다른 지시문에 전달하여 사용할 수도 있습니다. 조건부 분기는 값이 0이 아닌 경우 실행되는 "if" 블록을 사용할 수 있지만 "else" 블록이 없으므로 여러 코드 경로를 처리하려면 "switch"를 사용하여 여러 경우를 처리하고 " 기본" 폴백 옵션. 루프는 "for" 루프를 사용하여 수행할 수 있으며 구문은 다른 고급 언어와 다르지만 동일한 기본 기능을 제공합니다. 내장 함수는 "function" 키워드를 사용하여 정의할 수 있으며 고급 언어의 함수 정의와 유사합니다.
Yul의 대부분의 기능은 인라인 어셈블리 블록을 사용하여 Solidity에서 노출됩니다. 이를 통해 개발자는 추상화를 중단하거나 사용자 지정 기능을 작성하거나 높은 수준의 구문에서 사용할 수 없는 기능에 Yul을 사용할 수 있습니다. 그러나 이 기능을 사용하려면 호출 데이터, 메모리 및 스토리지 측면에서 Solidity의 동작을 깊이 이해해야 합니다.
독특한 기능도 있습니다. "datasize", "dataoffset" 및 "datacopy" 함수는 문자열 별칭을 통해 Yul 개체를 조작합니다. "setimmutable" 및 "loadimmutable" 함수를 사용하면 변경 불가능한 매개변수를 생성자에서 설정하고 로드할 수 있지만 사용이 제한됩니다. "memoryguard" 기능은 주어진 범위의 메모리만 할당되어 컴파일러가 추가 최적화를 위해 보호된 범위를 넘어 메모리를 사용할 수 있음을 의미합니다. 마지막으로 "단어"는 Yul 컴파일러가 알지 못하는 지시문을 사용할 수 있도록 합니다.
다음은 Yul로 작성된 간단한 계약서입니다.

좋은 EVM DSL의 특징
좋은 EVM DSL은 여기에 나열된 각 언어의 강점과 약점에서 배워야 하며 조건문, 패턴 일치, 루프, 함수 등과 같은 거의 모든 현대 언어의 기본 사항도 다루어야 합니다. 코드는 모호하지 않아야 하며 코드 미학 또는 가독성을 위해 최소한의 암시적 추상화를 추가해야 합니다. 고부담의 정확성이 중요한 환경에서는 모든 코드 라인을 명시적으로 설명할 수 있어야 합니다. 또한 잘 정의된 모듈 시스템은 훌륭한 언어의 핵심이 되어야 합니다. 어떤 항목이 어떤 범위에 정의되어 있고 어떤 항목이 액세스 가능한지 명확하게 명시해야 합니다. 모듈의 모든 항목은 기본적으로 비공개여야 하며 명시적으로 공개된 항목만 외부에서 공개적으로 액세스할 수 있습니다.
EVM과 같이 리소스가 제한된 환경에서는 효율성이 중요합니다. 효율성은 종종 매크로를 통한 컴파일 타임 코드 실행, 잘 설계된 재사용 가능한 라이브러리를 생성하는 풍부한 유형 시스템, 일반적인 온체인 상호 작용을 위한 래퍼와 같은 저비용 추상화를 제공함으로써 달성됩니다. 매크로는 컴파일 시간에 코드를 생성하므로 일반적인 작업에 대한 상용구 코드를 줄이는 데 유용하며 Huff와 같은 경우 코드 크기와 런타임 효율성을 절충하는 데 사용할 수 있습니다. 풍부한 유형 시스템은 더 표현력이 풍부한 코드, 런타임 전에 오류를 포착하기 위한 더 많은 컴파일 시간 검사를 허용하고 유형 검사 컴파일러 내장 기능과 결합할 때 많은 인라인 어셈블리가 필요하지 않을 수 있습니다. 제네릭은 또한 nullable 값(예: 외부 코드)을 "옵션" 유형으로 래핑하거나 오류가 발생하기 쉬운 작업(예: 외부 호출)을 "결과" 유형으로 래핑할 수 있습니다. 이 두 가지 유형은 라이브러리 작성자가 실패한 결과를 복원하는 트랜잭션 또는 코드 경로를 정의하여 개발자가 각 결과를 처리하도록 강제할 수 있는 방법의 예입니다. 그러나 이들은 런타임에 간단한 조건부 점프로 해결되는 컴파일 타임 추상화라는 점을 명심하십시오. 개발자가 컴파일 시간에 모든 결과를 처리하도록 하면 초기 개발 시간이 늘어나지만 런타임에 예상치 못한 일이 훨씬 적어지는 이점이 있습니다.
유연성은 개발자에게도 중요하므로 복잡한 작업의 기본값은 안전하고 덜 효율적인 경로여야 하지만 더 효율적인 코드 경로 또는 지원되지 않는 기능을 사용해야 하는 경우가 있습니다. 이를 위해 인라인 어셈블리는 가드레일 없이 개발자에게 개방되어야 합니다. Solidity의 인라인 어셈블리는 단순성과 더 나은 옵티마이저 제공을 위해 일부 보호책을 마련하지만 개발자는 실행 환경을 완전히 제어해야 할 때 이러한 권한을 부여받아야 합니다.
결론적으로
결론적으로
원본 링크


