Solidity 소스 파일의 배치

Solidity 소스 파일의 배치 (Layout of a Solidity Source File)

Solidity 소스 파일은 임의 개수의 컨트랙트 정의, import, pragma, using for 지시문과 구조체, enum, 함수, 에러, 상수 변수 정의를 담을 수 있어요. 소스 파일을 올바르게 시작하려면 SPDX 라이선스 식별자, pragma 사용, import 문법, 주석 규칙을 이해해야 해요.

출처: 문서

본문

소스 파일은 임의 개수의 컨트랙트 정의, import, pragma, using for 지시문과 struct, enum, function, error, constant 변수 정의를 담을 수 있어요.

SPDX 라이선스 식별자 (SPDX License Identifier)

스마트 컨트랙트에 대한 신뢰는 그 소스 코드를 사용할 수 있을 때 더 잘 확립될 수 있어요. 소스 코드를 공개하는 것은 항상 저작권에 관한 법적 문제를 건드리기 때문에, Solidity 컴파일러는 기계가 읽을 수 있는 SPDX 라이선스 식별자의 사용을 장려해요.

모든 소스 파일은 그 라이선스를 나타내는 주석으로 시작해야 해요:

// SPDX-License-Identifier: MIT

컴파일러는 라이선스가 SPDX가 허용한 목록의 일부인지 검증하지 않지만, 제공된 문자열을 바이트코드 메타데이터에 포함해요. 라이선스를 지정하고 싶지 않거나 소스 코드가 오픈소스가 아니라면 특별한 값 UNLICENSED를 사용해요. UNLICENSED(사용 허용 없음, SPDX 라이선스 목록에 없음)는 UNLICENSE(모든 사람에게 모든 권리 부여)와 다르다는 점에 주의해요. Solidity는 npm 권장 사항을 따릅니다.

물론 이 주석을 제공한다고 해서 각 소스 파일에 특정 라이선스 헤더를 언급해야 하거나 원래 저작권 보유자를 언급해야 하는 것 같은 다른 라이선스 관련 의무에서 벗어나는 것은 아니에요. 컴파일러는 이 주석을 파일 어디에서든 파일 수준에서 인식하지만, 파일 맨 위에 두는 걸 권장해요. SPDX 라이선스 식별자 사용 방법에 대한 더 많은 정보는 SPDX 웹사이트에서 찾을 수 있어요.

Pragma (Pragmas)

pragma 키워드는 특정 컴파일러 기능이나 검사를 활성화하는 데 사용해요. pragma 지시문은 항상 소스 파일에 국한되므로, 프로젝트 전체에서 활성화하려면 모든 파일에 pragma를 추가해야 해요. 다른 파일을 import하면 그 파일의 pragma가 임포트하는 파일에 자동으로 적용되지 않아요.

버전 Pragma (Version Pragma)

소스 파일은 호환되지 않는 변경을 도입할 수 있는 미래 컴파일러 버전으로의 컴파일을 거부하도록 버전 pragma로 주석 처리될 수 있고(그리고 해야 하고) 있어요. 우리는 그러한 변경을 절대 최소로 유지하고, 의미의 변경이 문법의 변경도 요구하는 방식으로 도입하려고 하지만, 항상 가능한 것은 아니에요. 그 때문에 적어도 breaking change를 포함하는 릴리스에 대해서는 changelog를 훑어보는 게 항상 좋은 생각이에요. 그런 릴리스는 항상 0.x.0 또는 x.0.0 형태의 버전을 가져요.

버전 pragma는 다음과 같이 사용돼요: pragma solidity ^0.5.2;

위 줄이 있는 소스 파일은 버전 0.5.2보다 이전 컴파일러로 컴파일되지 않고, 버전 0.6.0부터 시작하는 컴파일러에서도 동작하지 않아요(이 두 번째 조건은 ^로 추가됨). 버전 0.6.0까지 breaking change가 없으므로, 코드가 의도한 대로 컴파일된다고 확신할 수 있어요. 컴파일러의 정확한 버전은 고정되지 않으므로 버그픽스 릴리스가 여전히 가능해요.

컴파일러 버전에 대한 더 복잡한 규칙을 지정하는 것도 가능하며, 이들은 npm에서 사용하는 것과 같은 문법을 따라요.

참고 (Note)

버전 pragma를 사용해도 컴파일러 버전이 바뀌지 않아요. 컴파일러 기능을 활성화하거나 비활성화하지도 않아요. 컴파일러가 그 버전이 pragma가 요구하는 것과 일치하는지 확인하도록 지시할 뿐이에요. 일치하지 않으면 컴파일러가 에러를 내요.

ABI Coder Pragma

pragma abicoder v1 또는 pragma abicoder v2를 사용해 ABI 인코더와 디코더의 두 구현 사이에서 선택할 수 있어요. 새 ABI coder(v2)는 임의로 중첩된 배열과 구조체를 인코딩·디코딩할 수 있어요. 더 많은 타입을 지원하는 것 외에 더 광범위한 검증과 안전 검사를 포함해, 가스 비용이 더 높을 수 있지만 보안도 더 높아요. Solidity 0.6.0부터 비-실험적(non-experimental)로 간주되고 Solidity 0.8.0부터 기본으로 활성화돼요. 옛 ABI coder는 여전히 pragma abicoder v1;으로 선택할 수 있어요.

경고 (Warning)

ABI coder v1은 비권장이며 제거될 예정이에요. 대신 ABI coder v2를 사용해요.

새 인코더가 지원하는 타입 집합은 옛 인코더가 지원하는 것의 엄격한 상위집합이에요. 그것을 사용하는 컨트랙트는 제한 없이 그것을 사용하지 않는 것과 상호작용할 수 있어요. 그 반대는 non-abicoder v2 컨트랙트가 새 인코더만 지원하는 타입을 디코딩해야 하는 호출을 시도하지 않는 한 가능해요. 컴파일러는 이를 감지하고 에러를 낼 거예요. 컨트랙트에 단순히 abicoder v2를 활성화하는 것만으로도 에러를 없앨 수 있어요.

참고 (Note)

이 pragma는 그것이 활성화된 파일에 정의된 모든 코드에 적용되며, 그 코드가 결국 어디에 놓이는지와 무관해요. 즉 ABI coder v1로 컴파일되도록 선택된 소스 파일의 컨트랙트가 다른 컨트랙트에서 상속해 새 인코더를 사용하는 코드를 여전히 포함할 수 있다는 뜻이에요. 새 타입이 내부적으로만 사용되고 외부 함수 시그니처에는 없으면 허용돼요.

참고 (Note)

Solidity 0.7.4까지는 pragma experimental ABIEncoderV2로 ABI coder v2를 선택하는 것이 가능했지만, 기본이기 때문에 coder v1을 명시적으로 선택하는 것은 불가능했어요.

실험적 Pragma (Experimental Pragma)

두 번째 pragma는 실험적 pragma예요. 아직 기본으로 활성화되지 않은 컴파일러나 언어의 기능을 활성화하는 데 사용할 수 있어요. 현재 다음 실험적 pragma가 지원돼요:

ABIEncoderV2

ABI coder v2가 더 이상 실험적으로 간주되지 않기 때문에, Solidity 0.7.4부터 pragma abicoder v2로 선택할 수 있어요(위 참고).

SMTChecker

pragma experimental SMTChecker;을 사용하면 SMT 솔버를 조회해 얻는 추가 안전 경고를 받게 돼요. 이 구성 요소는 아직 Solidity 언어의 모든 기능을 지원하지 않고 많은 경고를 출력할 가능성이 높아요. 지원되지 않는 기능을 보고하면 분석이 완전히 건전하지 않을 수 있어요.

참고 (Note)

SMTChecker pragma는 비권장이며 제거될 거예요. SMTChecker를 활성화하려면 컴파일러를 호출할 때 엔진을 선택하면 돼요.

다른 소스 파일 임포트하기 (Importing other Source Files)

문법과 의미 (Syntax and Semantics)

Solidity는 코드를 모듈화하는 데 도움이 되는 import 문을 지원하는데, 이는 JavaScript(ES6부터)에서 사용할 수 있는 것과 비슷해요. 그러나 Solidity는 기본 내보내기(default export) 개념을 지원하지 않아요.

전역 수준에서 다음 형태의 import 문을 사용할 수 있어요:

open in Remix

import "filename";

filename 부분을 임포트 경로(import path)라고 불러요. 이 문은 "filename"에서(및 거기서 임포트된 기호들에서) 모든 전역 기호를 현재 전역 스코프로 임포트해요(ES6와 다르지만 Solidity에는 하위 호환). 이 형태는 네임스페이스를 예측할 수 없게 오염시키기 때문에 사용이 권장되지 않아요. "filename" 안에 새 최상위 항목을 추가하면, 그 항목은 이런 방식으로 "filename"에서 임포트하는 모든 파일에 자동으로 나타나요. 특정 기호를 명시적으로 임포트하는 것이 더 좋아요.

다음 예시는 멤버가 "filename"의 모든 전역 기호인 새 전역 기호 symbolName을 만들어요:

open in Remix

import * as symbolName from "filename";

이는 모든 전역 기호가 symbolName.symbol 형식으로 사용 가능해지는 결과를 내요. ES6의 일부가 아닌 이 문법의 변형이지만 어쩌면 유용한 것은:

open in Remix

import "filename" as symbolName;

이는 import * as symbolName from "filename";와 동일해요.

이름 충돌이 있으면 임포트하면서 기호 이름을 바꿀 수 있어요. 예를 들어 아래 코드는 "filename" 안의 symbol1과 symbol2를 각각 참조하는 새 전역 기호 alias와 symbol2를 만들어요.

open in Remix

import {symbol1 as alias, symbol2} from "filename";

임포트 경로 (Import Paths)

모든 플랫폼에서 재현 가능한 빌드를 지원하기 위해, Solidity 컴파일러는 소스 파일이 저장된 파일시스템의 세부 사항을 추상화해야 해요. 이런 이유로 임포트 경로는 호스트 파일시스템의 파일을 직접 참조하지 않아요. 대신 컴파일러는 각 소스 유닛에 고유한 소스 유닛 이름(불투명하고 구조화되지 않은 식별자)이 할당된 내부 데이터베이스(가상 파일시스템, VFS)를 유지해요. import 문에 지정된 임포트 경로는 소스 유닛 이름으로 변환되고 이 데이터베이스에서 해당 소스 유닛을 찾는 데 사용돼요.

표준 JSON API를 사용하면 컴파일러 입력의 일부로 모든 소스 파일의 이름과 내용을 직접 제공할 수 있어요. 이 경우 소스 유닛 이름은 정말 임의적이에요. 그러나 컴파일러가 소스 코드를 자동으로 찾아 VFS에 로드하기를 원한다면, 소스 유닛 이름이 임포트 콜백이 그것을 찾을 수 있게 하는 방식으로 구조화되어야 해요.

명령줄 컴파일러를 사용할 때 기본 임포트 콜백은 호스트 파일시스템에서만 소스 코드 로드를 지원해요. 즉 소스 유닛 이름이 경로여야 한다는 뜻이에요. 일부 환경은 더 다재다능한 커스텀 콜백을 제공해요. 예를 들어 Remix IDE는 HTTP, IPFS, Swarm URL에서 파일을 가져오거나 NPM 레지스트리의 패키지를 직접 참조하게 해주는 콜백을 제공해요.

가상 파일시스템과 컴파일러가 사용하는 경로 해석 로직에 대한 완전한 설명은 경로 해석(Path Resolution)을 참고해요.

주석 (Comments)

한 줄 주석(//)과 여러 줄 주석(/*...*/)이 가능해요.

open in Remix

// This is a single-line comment.

/*
This is a
multi-line comment.
*/

참고 (Note)

한 줄 주석은 UTF-8 인코딩의 어떤 유니코드 줄 종결자(LF, VF, FF, CR, NEL, LS 또는 PS)로도 종료돼요. 종결자는 주석 후에도 여전히 소스 코드의 일부이므로, ASCII 기호(NEL, LS, PS)가 아니면 파서 에러로 이어져요.

게다가 NatSpec 주석이라는 또 다른 유형의 주석이 있으며, 이는 스타일 가이드에서 자세히 다뤄져요. 삼중 슬래시(///) 또는 이중 별표 블록(/** ... */)으로 작성되고, 함수 선언이나 문장 바로 위에 사용해야 해요.

더 알아보기 (Learn more)