이더리움에서 스마트 컨트랙트 코드를 실행하는 프로그램을 EVM(Ethereum Virtual Machine)이라고 합니다. EVM은 스마트 컨트랙트를 컴파일해서 얻어진 바이트 코드를 실행합니다. 이 때 바이트 코드란 EVM에서 정의하는 명령어, 즉 OP 코드들의 집합을 의미합니다.
EVM은 CPU가 명령어를 실행하는 방법과 유사한데, CPU는 명령어와 파라미터를 레저스터라는 임시 저장공간에 옮겨서 실행하는 반면, EVM같은 경우는 명령어들을 순차적으로 실행하면서 파라미터와 연산의 결과값을 스택이라고 불리는 메모리 공간에 유지하면서 실행한다는 점이 일반적인 CPU와 차이점이 있습니다.
OP 코드 : EVM 명령어를 16진수로 표현
바이트 코드 : 스마트컨트랙트 -> 컴파일 -> 바이트 코드
이러한 명령어들은 각각 16진수로 표현되는 OP 코드로 표현될 수 있습니다. 여기서 바이트코드란 스마트 컨트랙트의 소스 코드를 컴파일하여 얻어진 결과물로서, OP 코드와 파라미터들의 집합으로 볼 수가 있습니다.
EVM에서 사용하는 스택은 32바이트 데이터를 최대 1024개까지 저장할 수 있는 메모리입니다. 따라서 재귀 호출이나 지역변수를 많이 선언할 경우에는 스택이 부족할 수 있습니다. 그럴 경우에는 스마트 컨트랙트가 정상적으로 실행되기 어렵습니다.
스마트 컨트랙트를 올바르게 구현하기 위해서는 데이터들이 저장되는 공간을 정확하게 이해할 필요가 있습니다. 솔리디티로 작성된 스마트 컨트랙트의 데이터들이 저장되는 공가은 크게 스토리지, 메모리, 콜데이터가 있습니다.
EVM은 3개의 데이터 저장공간, 즉, 스토리지, 메모리, 콜데이터라는 저장공간과 EVM 명령어 실행에 직접적으로 사용되는 스택이라는 저장공간으로 구성이 되어 있다고 볼 수 있습니다. 대부분의 EVM의 명령어들은 스택에 저장된 데이터를 직접적으로 참조하기 때문에 나머지 3개의 저장공간에 저장된 데이터들은 필요시 스택으로 이동이 되었다가 연산이 처리된 후에는 다시 원래의 저장 공간으로 카피가 발생하는 저장 공간들 사이에 데이터 이동이 진행된다고 볼 수가 있습니다.
솔리디티에서는 기본적으로 변수와 함수의 파라미터들이 존재할 저장 위치가 정해져 있습니다. 다만 지역변수와 함수 인자들에 한해 제한적으로 프로그래머가 저장위치를 변경 또는 지정하는 것이 가능합니다.
여기에는 몇 가지 규칙들이 있는데요, 먼저 상태변수는 항상 스토리지에 저장이 되어야하고, 저장위치를 프로그래머가 임의로 변경하는 것은 불가능합니다.
지역변수와 함수인자는 기본적으로 메모리에 저장이 되는데 밸류타입의 변수는 저장 위치의 변경이 불가능하고 참조형 변수는 프로그래머가 저장 위치를 제한적으로 변경하는 것이 가능합니다.
마지막으로 외부 컨트랙트를 호출할 경우 함수의 인자는 항상 콜데이터에 저장되어야하고 이 저장위치를 변경하는 것은 불가능 합니다.
솔리디티에서는 지역변수로 사용되는 참조형 변수의 저장위치를 바꾸는 것을 허용하고 있는데요. 이 때 저장 위치에 대한 규칙과 의미가 꽤 까다로운 편입니다.
배열, 구조체, 매핑과 같은 참조형 타입 변수를 함수내에서 선언할 때, 저장위치를 메모리로 지정하면 실제 데이터 객체가 생성되고요. 반면에 저장위치를 스토리지로 지정할 경우에 단지 레퍼런스만 생성되는 특징을 가지고 있습니다.
몇 가지 유의사항도 있는데, 매핑을 반드시 스토리지로 명시해야하고, 상태변수를 이용한 초기화가 필요합니다. 그리고 배열과 구조체의 저장 위치를 스토리지로 지정할 경우에는 반드시 스토리지에서 선언된 상태변수로 초기화하는 것이 필요합니다.
참조형 타입의 변수의 저장 위치가 꽤 까다로운데 레퍼런스, 참조형 타입의 변수간의 할당 연산, 즉 assignment 연산을 할 때 규칙과 의미도 꽤 복잡합니다. 중요한 규칙이 두 가지가 있는데요.
먼저 스토리지에 저장된 참조형타입의 지역변수에 스토리지에 저장된 다른 유형의 변수를 할당, assignment 할 때는 대개 레퍼런스가 할당이 됩니다.
두 번째 규칙은 메모리에 저장된 참조형 타입의 지역변수 간의 할당은 레퍼런스가 할당이 됩니다.
그리고 이 두 가지 규칙을 벗어난 나머지 모든 경우에 대해서는 대부분 값이 복사가 됩니다.
마지막으로 레퍼런스, 참조형 변수를 함수인자로 사용할 때도 주의가 필요한데요. 이것이 참조형 타입임에도 불구하고 함수 인자로 사용될 경우에는 보통 Call-by-Value 형태로 사용이 됩니다. 따라서 함수인자로 사용된 참조형변수를 함수 내부에서 변경할 경우 함수 밖에 있는 원본 객체의 값은 변하지가 않습니다.
구조체를 함수 인자로 사용할 경우에는 ABIEncoderV2를 pragma로 선언해야합니다.
또한 매핑을 함수의 인자로 사용하는 것은 기본적으로 불가능합니다. 다만, internal함수에서는 사용 가능하며, 매핑을 함수의 인자로 사용하고자 하는 경우에는 상태 변수로 선언한 후 매핑의 키를 함수 인자로 대신 사용할 것을 권장합니다.