Opcode
에 대해 배우기 전 EVM
을 간단히 짚고 넘어가겠습니다.
EVM
은 이더리움 가상 머신으로 다들 알고계실겁니다.
우리가 흔히 geth
같은 이더리움 클라이언트를 설치하면 스마트 계약을 싱행하기 위해 특별히 제작된 경량 운영 체제인 EVM
도 함께 제공됩니다.
EVM
.. 쉽게 풀어볼까요? 그냥 간단히 이더리움 블록체인의 실행환경이라고 생각합시다.
우리가 이더리움 스마트 컨트랙트를 작성할 때, 주로 Solidity
를 사용합니다.
Solidity
는 고수준 언어로 인간이 이해할 수 있는 언어입니다. 인간은 이해할 수 있는데 기계는 이해할 수 없다..?
그렇다면 EVM
은 어떻게 인간만 이해할 수 있는 코드를 해석해서 스마트컨트랙트를 실행시키는걸까요? 여기서 많이들 들어보신 바이트코드가 나옵니다.
Solidity
컴파일러를 통해 Solidity
로 작성된 스마트 컨트랙트를 네트워크에 배포하기전에 컴파일 많이들 해보셨을겁니다.
이러한 컴파일을 통해 EVM
이 우리가 Solidity
로 작성한 스마트 컨트랙트를 이해할 수 있도록 변환해주는 것입니다.
그렇다면 여기까지 인간이 이해할 수 있는 언어인 Soildity
를 컴파일하여 EVM
같은 가상 머신에서 실행할 수 있도록 한다.로 정리할 수 있겠습니다.
자 그럼 실제 Remix IDE
툴을 이용해서 간단한 스마트 컨트랙트를 작성해 바이트코드를 확인해볼까요?
컴파일할 스마트 컨트랙트는 아래와 같습니다.
컴파일을 하였으면 하단에 Compilation Details
를 클릭해서 우리가 작성한 스마트 컨트랙트에 대한 바이트코드를 확인해봅시다.
흠.. 형태를 보아하니 object
는 컴파일된 바이트코드인것 같고.. 근데 Opcodes
? 바이트코드만 있으면 EVM
에서 스마트 컨트랙트를 실행할 수 있다고 했는데..
바로 구글링 해봅시다.
컴퓨터 과학에서 명령 코드(opcode←operation code, instruction syllable, instruction parcel, opstring[1][2][3][4][5][6][7])는 기계어의 일부이며 수행할 명령어를 나타내는 부호를 말한다. 이에 대한 규격과 형식은 프로세서 명령어 집합에 나와 있다. (프로세서는 일반 CPU일 수도 있고 특별한 처리 장치일 수도 있다) 기계어 명령어(Instruction)는 명령어를 나타내는 opcode를 가지며, 일반적으로 피연산자를 나타내는 하나 이상의 지정자를 가진다.
비전공자인 저에게는 너무나 어려운 말들만 써있군요.. 간단히 정리해봅시다.
Opcode
)는 기계어의 일부이며 수행할 명령어를 나타내는 부호를 말한다.Instruction
)는 명령어를 나타내는 opcode
를 가진다.대충 이정도로 정리해보면 쉽겠네요.
자, 그렇다면 바이트코드는 EVM
이 이해하는 코드입니다. 그럼 위키트리에 나온 내용을 이런식으로 해석해보면 어떨까요?
Opcode
)를 가진다.흠.. 뭔가 그럴듯해 보이죠? 바이트코드는 옵코드와 매칭이 된다라.. 당장 바이트코드와 옵코드를 가져와 비교해봅시다.
60806040526008600055348015601457600080fd5b50603f8060226000396000f3fe6080604052600080fdfea26469706673582212206c0b14387e23dca73af0f7e35c45eafa12422727271941b551af3573a43002ba64736f6c63430008110033
PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x8 PUSH1 0x0 SSTORE CALLVALUE DUP1 ISZERO PUSH1 0x14 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3F DUP1 PUSH1 0x22 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 PUSH13 0xB14387E23DCA73AF0F7E35C45 0xEA STATICCALL SLT TIMESTAMP 0x27 0x27 0x27 NOT COINBASE 0xB5 MLOAD 0xAF CALLDATALOAD PUSH20 0xA43002BA64736F6C634300081100330000000000
우리같은 인간은 바이트코드를 아무리 본다고 해도 해석할 수 없습니다. 기계가 아니니까요. 구글의 힘을 빌려 다시한번 구글링을 해봅시다.
EVM Opcode
를 검색하여 정보를 얻어봅시다. 하지만 여러분들의 시간은 소중하니 링크를 올려놓을게요
위 사이트로 접속후 첫번째 Opcode
인 PUSH1
을 검색하면 60
의 Stack
을 가진것을 확인할 수 있습니다.
보아하니 바이트코드와 Opcode
가 정말 매칭되는것을 확인할 수 있습니다.
흠.. 그렇다면 바이트코드와 옵코드가 매칭인건 알겠는데, 바이트코드만 있으면 되는거 아닌가?
옵코드는 왜 필요한거지? 라는 의문이 들 수 있습니다.
여기서 우리는 EVM
에 동작방식에 대해 더 자세히 알아야 할 필요가 있습니다.
자, 우리는 위에서 바이트코드와 Opcode
가 매칭된다는것을 확인했습니다.
따라서 우리는 EVM
이 컴파일한 바이트코드와 매칭되는 Opcode
를 실행한다는 것을 이해할 수 있습니다.
그렇다면 어떻게 실행하느냐도 간단히 알아볼까요?
EVM
은 스택방식입니다. 다들 LIFO(Last In First Out)
으로 한번씩은 들어보셨을 겁니다.
간단히 예를 들어보도록 하겠습니다
uint i = 2 + 2 * 2;
우리는 위같은 산술식에서 i
는 6이라는걸 알 수 있습니다. 2 * 2 를 먼저 계산하고 후에 + 2를하여 나온 값임을 알 수 있죠.
하지만 스택방식에서는 아래와 같이 LIFO
원리로 작동합니다.
2 2 * 2 +
스택에 2를 넣고 다음으로 2를 넣은 다음 곱셈 작업을 합니다.
결과적으로 스택의 맨 위에는 4가 있죠, 이제 4 위에 다시 숫자 2를 추가하고 이를 더합니다.
따라서 스택의 최종값은 6이 됩니다.
이러한 유형의 산술을 역폴란드 표기법이라고 합니다.
자, 여기까지 EVM
, 바이트코드, 옵코드에 대해 알아보았습니다.
최종적으로 EVM
은 솔리디티 언어로 작성된 스마트 컨트랙트를 바이트코드로 변환하면 그에 해당하는 Opcode
를 실행해준다. 이런식으로 정리할 수 있겠습니다.
그렇다면 어차피 옵코드를 실행한다는건데 옵코드로 컨트랙트를 작성할 수는 없을까요?
물론, 가능합니다.
스마트 컨트랙트 개발자라면 한번쯤은 마주쳤을 Assembly
가 바로 솔리티디 내부에서 옵코드 작성을 가능하게 해줍니다.
간단하게 예시로 사용방법을 알아볼까요?
function addAssembly(uint x, uint y) public pure returns (uint) {
assembly {
let result := add(x, y)
mstore(0x0, result)
return(0x0, 32)
}
}
위의 addAssembly
함수는 옵코드 add
를 사용해서 x
와 y
를 더한 값을 :=
키워드로 result
에 담고, 옵코드 mstore
를 사용해서 메모리 주소 0x0
에 result
의 값을 저장합니다.
마지막으로 0x0
메모리 주소에 32Byte
를 반환하는 코드입니다.
그렇다면 Assembly 왜 사용하는걸까요? 그냥 솔리디티로 x + y
하는게 더 편하지 않나? 라는 생각이 드실겁니다.
위의 예시에서 살짝 봤듯이 우리는 메모리주소 0x0
에 값을 저장하는것을 알 수 있습니다.
솔리디티에서는 할 수 없는 미세한 조정도 가능하다는것을 확인할 수 있죠, 또한 컴파일된 바이트코드와 매칭되는 Opcode
를 실행하는것이 아닌 순수 Opcode
를 통해 EVM
과 상호작용을 하기 때문에 가스비가 절감됩니다.
이상으로 EVM과 조금은 더 친해지는 시간을 가져보았습니다~