Ethereum/EVM

EVM과 Ethereum Opcode

dev_swan 2023. 12. 21.

✏️ EVM

EVM(Ethereum Virtual Machine)은 이더리움 가상 머신으로, 우리가 흔히 geth 같은 이더리움 클라이언트를 설치하면 스마트 계약을 실행하기 위해 특별히 제작된 경량 운영 체제인 EVM도 함께 제공됩니다. 

우리가 이더리움 스마트 컨트랙트를 작성할 때, 주로 Solidity를 사용합니다. Solidity는 고수준 언어로 인간이 이해할 수 있는 언어입니다.

 

인간은 이해할 수 있는데 기계는 이해할 수 없다..?

그렇다면 EVM은 어떻게 인간만 이해할 수 있는 코드를 해석해서 스마트컨트랙트를 실행시키는걸까요? 여기서 많이 들어보신 바이트코드가 나옵니다.

바이트코드 ( ByteCode )

스마트 컨트랙트를 이더리움 네트워크에 배포하기 전, 컴파일러를 통해 Solidity로 작성된 스마트 컨트랙트를 컴파일 많이들 해보셨을 겁니다.

이러한 컴파일을 통해 EVM이 우리가 Solidity로 작성한 스마트 컨트랙트를 이해할 수 있도록 변환해 주는 것입니다.

그렇다면 여기까지 인간이 이해할 수 있는 언어인 Soildity를 컴파일하여 EVM 같은 가상 머신에서 실행할 수 있도록 한다고 정리할 수 있겠습니다.

자 그럼 실제 Remix IDE툴을 이용해서 간단한 스마트 컨트랙트를 작성해 바이트코드를 확인해 볼까요?

더보기

💡 Remix IDE

Remix IDE는 이더리움 스마트 컨트랙트 개발을 위한 오픈 소스 웹 및 데스크톱 애플리케이션입니다. 이 도구는 스마트 컨트랙트를 작성, 테스트, 디버깅 및 배포하는 데 사용됩니다.

 

컴파일할 스마트 컨트랙트는 아래와 같습니다.

 

예시 스마트 컨트랙트


컴파일을 하였으면 하단에 Compilation Details를 클릭해서 우리가 작성한 스마트 컨트랙트에 대한 바이트코드를 확인해 봅시다.

 

스마트 컨트랙트 컴파일 디테일


형태를 보면 object는 컴파일된 바이트코드인 것을 확인할 수 있습니다. 근데 Opcodes? 바이트코드만 있으면 EVM에서 스마트 컨트랙트를 실행할 수 있다고 했는데..

 

바로 구글링 해봅시다.

 

✏️ Opcode

 

Opcde - 위키백과


위키백과에 있는 내용을 간단히 요약하면

 

  • 컴퓨터 과학에서 명령 코드(Opcode)는 기계어의 일부이며 수행할  명령어를 나타내는 부호를 말한다.
  • 기계어 명령어(Instruction)는 명령어를 나타내는 opcode를 가진다.

자, 그렇다면 바이트코드는 EVM이 이해하는 코드입니다. 그럼 위키트리에 나온 내용을 이런 식으로 해석해 보면 어떨까요?

바이트코드는는 명령어를 나타내는 명령코드(Opcode)를 가진다.

흠.. 뭔가 그럴듯해 보이죠? 바이트코드는 옵코드와 매칭이 된다라.. 당장 바이트코드와 옵코드를 가져와 비교해 봅시다.

바이트 코드

60806040526008600055348015601457600080fd5b50603f8060226000396000f3fe6080604052600080fdfea26469706673582212206c0b14387e23dca73af0f7e35c45eafa12422727271941b551af3573a43002ba64736f6c63430008110033

Opcode

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를 검색하여 정보를 얻어봅시다. 하지만 여러분들의 시간은 소중하니 링크를 올려놓을게요

 

https://ethereum.org/ko/developers/docs/evm/opcodes/


위 사이트로 접속 후 첫 번째 Opcode인 PUSH1을 검색하면 60의 Stack을 가진 것을 확인할 수 있습니다.

 

Ethereum Opcode - Ethereum


바이트코드와 Opcode가 정말 매칭되는 것을 확인할 수 있습니다.


흠.. 그렇다면 바이트코드와 옵코드가 매칭인 건 알겠는데, 바이트코드만 있으면 되는 거 아닌가?

 

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이 됩니다.

이러한 유형의 산술을 역폴란드 표기법이라고 합니다.

더보기

💡 역폴란드 표기법(Reverse Polish Notation, RPN)

역폴란드 표기법은 수학적 표현을 기술하는 방식 중 하나입니다. 이 방식은 연산자가 피연산자 뒤에 오는 특징을 가지고 있으며, 괄호를 줄이거나 제거할 수 있어 계산식을 간소화합니다.

 



자, 여기까지 EVM 동작 방식, 바이트코드, Opcode에 대해 알아보았습니다.

최종적으로 EVM은 Solidity 언어로 작성된 스마트 컨트랙트를 바이트코드로 변환하면 그에 해당하는 Opcode를 실행해 준다. 이렇게 정리할 수 있겠습니다.

그렇다면 어차피 Opcode를 실행한다는 건데 Opcode로 컨트랙트를 작성할 수는 없을까요?

 

물론, 가능합니다.

 

✏️ Assembly

스마트 컨트랙트 개발자라면 한 번쯤은 마주쳤을 Assembly가 바로 Solidity 내부에서 Opcode 작성을 가능하게 해 줍니다.

간단한 예시로 사용 방법을 알아보겠습니다.

function addAssembly(uint x, uint y) public pure returns (uint) {
     assembly {
       
         let result := add(x, y)
       
         mstore(0x0, result)
         
         return(0x0, 32)
     }
 }


위의 addAssembly 함수는 Opcode add를 사용해서 x와 y를 더한 값을 := 키워드로 result에 담고, Opcode mstore를 사용해서 메모리 주소 0x0에 result의 값을 저장한 다음 마지막으로 0x0 메모리 주소에 32Byte를 반환하는 코드입니다.


그렇다면 Assembly 왜 사용하는 걸까요? 그냥 Solidity로 'x + y' 하는 게 더 편하지 않나?라는 생각이 드실 겁니다.

위의 예시에서 살짝 봤듯이 우리는 메모리주소 0x0에 값을 저장하는 것을 알 수 있습니다.


Solidity에서는 할 수 없는 미세한 조정도 가능하다는 것을 확인할 수 있죠, 또한 컴파일된 바이트코드와 매칭되는 Opcode를 실행하는 것이 아닌 순수 Opcode를 통해 EVM과 상호작용을 하기 때문에 가스비가 절감됩니다.

댓글