Comprender cómo un contrato simple se divide en código de bytes

Estoy tratando de entender cómo se ven los contratos en términos de código de bytes y me cuesta hacerlo solo en base al papel amarillo. En particular, considere el siguiente contrato vacío:

pragma solidity ^0.4.17;

contract Simplest {
}

Usando el compilador Remix , puedo compilar en el siguiente código de bytes:

6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a7230582053a24015f887e1dd9fbd5e5cadb397bb5fb34e8aab7b5782d9e28dfd4e9862810029

Lo que se traduce en los siguientes códigos de operación:

PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x35 DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 MSTORE8 LOG2 BLOCKHASH ISZERO 0xf8 DUP8 0xe1 0xdd SWAP16 0xbd 0x5e 0x5c 0xad 0xb3 SWAP8 0xbb 0x5f 0xb3 0x4e DUP11 0xab PUSH28 0x5782D9E28DFD4E986281002900000000000000000000000000000000

1) ¿Qué representa esto? ¿Está destinado a ser el código de bytes para la función constructora?

2) Hay varias secuencias que no tienen sentido para mí, en particular:

ISZERO 0xf8 DUP8 0xe1 0xdd SWAP16

¿Por qué están 0xf8, 0xe1, 0xddsiguiendo códigos de operación que no toman ningún argumento? ¿Cuál es su propósito?

3) Una vez compilado el contrato, ¿dónde está el código de bytes para las "funciones" del contrato?

Sospecho que las 3 preguntas están bastante vinculadas, por lo tanto, las hago juntas.

La respuesta a esta pregunta le brinda ayuda para el punto 2) ethereum.stackexchange.com/questions/15050/… .

Respuestas (1)

Lo que tiene en el código de bytes aquí es el código de bytes del constructor/construcción del contrato. Cuando crea un contrato, el constructor se ejecuta, maneja cualquier argumento inicial o declaración que haga y también "crea" el código del contrato.

La forma en que esto se hace es a través de una declaración de devolución. El código de contrato implementado será lo que devuelva el constructor. Entonces, el código de bytes que publicó incluirá el código de bytes del constructor, así como el código del contrato implementado en sí. También incluye una tercera sección: algunos metadatos (una característica de Solidity). Desglosándolo, aquí están las 3 secciones distintas:

Código de bytes del constructor (básicamente un script de implementación):

0x6080604052348015600f57600080fd5b50603580601d6000396000f300

Esto se traduce en los siguientes códigos de operación, que he anotado un poco:

// Set free memory pointer (0x40) to 0x80
[1] PUSH1 0x80
[3] PUSH1 0x40
[4] MSTORE
// Check msg.value
[5] CALLVALUE
[6] DUP1
[7] ISZERO
// If msg.value == 0, JUMP to 0x0F (15)
[9] PUSH1 0x0f
[10] JUMPI
// Otherwise, the next 3 instructions do: revert(0, 0)
[12] PUSH1 0x00
[13] DUP1
[14] REVERT
// Here's 0x0F, which has a corresponding JUMPDEST
[15] JUMPDEST
[16] POP
// Now we need the constructor to set up the deployment bytecode:
// 0x35 is presumably the length of the bytecode to return
[18] PUSH1 0x35
[19] DUP1
// 0x1D is the location in the bytecode from which the CODECOPY starts (29)
[21] PUSH1 0x1d
// 0x00 is where the code is copied to, in memory
[23] PUSH1 0x00
[24] CODECOPY
// Now we have the bytecode of the contract in memory, starting at 0x00 (and 0x35 bytes long). We DUP1'd the length earlier, so we can just push 0x00 and RETURN. The RETURN opcode will return 0x35 bytes, starting at position 0x00 in memory.
[26] PUSH1 0x00
[27] RETURN
[28] STOP 
// A final STOP at 28 (0x1C) marks the end of the constructor bytecode. The code copied started at 0x1D, so the next chunk of bytecode will be the deployed code

Y ahora aquí está el código de bytes del contrato (con metadatos al final):

0x6080604052600080fd00a165627a7a7230582053a24015f887e1dd9fbd5e5cadb397bb5fb34e8aab7b5782d9e28dfd4e9862810029

Nuevamente, traducido a códigos de operación:

// Set free memory pointer (0x40) to 0x80
[30] PUSH1 0x80
[32] PUSH1 0x40
[33] MSTORE
// The next 3 opcodes simply push 0 twice, then REVERT(0, 0)
[35] PUSH1 0x00
[36] DUP1
[37] REVERT
// And, a final STOP to mark the end of contract bytecode!
[38] STOP
// This next bit is confusing when translated directly from opcodes, because it's simply the metadata Solidity appends to the end of bytecode. It's not meant to be executed
[39] LOG1
[46] PUSH6 0x627a7a723058
[47] SHA3
[48] MSTORE8
[49] LOG2
[50] BLOCKHASH
[51] ISZERO
[52] 'f8'(Unknown Opcode)
[53] DUP8
[54] 'e1'(Unknown Opcode)
[55] 'dd'(Unknown Opcode)
[56] SWAP16
[57] 'bd'(Unknown Opcode)
[58] '5e'(Unknown Opcode)
[59] '5c'(Unknown Opcode)
[60] 'ad'(Unknown Opcode)
[61] 'b3'(Unknown Opcode)
[62] SWAP8
[63] 'bb'(Unknown Opcode)
[64] '5f'(Unknown Opcode)
[65] 'b3'(Unknown Opcode)
[66] '4e'(Unknown Opcode)
[67] DUP11
[68] 'ab'(Unknown Opcode)

Aquí hay más información sobre los metadatos del contrato: https://solidity.readthedocs.io/en/v0.4.24/metadata.html

Entonces, para responder directamente a sus preguntas:

  1. Sí, este debe ser el código de bytes del constructor. Dado que el constructor necesita devolver el código de bytes de implementación, el código de bytes del constructor también incluye el código de bytes de implementación.

  2. Las secuencias a las que se refiere provienen de los metadatos del contrato y no están destinadas a ser ejecutadas.

  3. El contrato que compiló no tiene ninguna función, por lo que la totalidad del "código de bytes de tiempo de ejecución" es la sección de 10 códigos de operación del código de bytes implementado, que establece el puntero de memoria libre y se revierte inmediatamente. Un contrato típico con funciones hará algo ligeramente diferente: tomará los primeros 4 bytes de calldata (usando CALLDATALOAD) y los comparará con una serie de selectores de funciones. Cuando encuentra una coincidencia, lo hará JUMPen la posición de esa función en el código. Si no se encuentra ninguna coincidencia, ejecuta la función de reserva. Si eso no existe, ¡revierte!

¡Espero haberlo aclarado! ¡Siéntete libre de hacer más preguntas!

¿ STOPSiempre se usa s para marcar el código de fin de contrato? ¿Puede una JUMPIdeclaración llegar más allá de a STOPy, en ese caso, cómo sabría el EVM qué STOPdesigna el código de constructor frente al código de contrato?
Que yo sepa, uno STOPmarca el final de un constructor y el otro marca el final del código de bytes del contrato (y el comienzo de los metadatos). En realidad, solo están ahí para demarcar, puedes JUMPpasarlos absolutamente. ¡Nada en el EVM detendrá eso!
Pregunta de novato, pero ¿por qué el puntero de memoria libre está configurado 0x40inicialmente? ¿Y cuál es la razón detrás de establecerlo específicamente para 0x80después?
@PaulRazvanBerg de ethereum.stackexchange.com/questions/56824/… IIUC este bloque de memoria se usa solo function $allocatey es "especial" solo para esta función. Pero, ¿por qué está configurado en 0x80y no en, 0x60por ejemplo? No sé. ¿De dónde viene esta función/a quién se le ocurrió/quién la está usando? No sé