Cómo determinar la dirección de un contrato inteligente antes de la implementación: usando CREATE2 para el intercambio de cifrado

El tema de blockchain no deja de ser una fuente no solo de todo tipo de exageraciones, sino también de ideas que son muy valiosas desde un punto de vista tecnológico. Por lo tanto, ella no pasó por alto a los habitantes de la ciudad soleada. Las personas miran, estudian e intentan trasladar su experiencia en el análisis informativo tradicional a los sistemas blockchain. Hasta ahora, solo un punto: uno de los desarrollos de Rostelecom-Solar es capaz de verificar la seguridad del software basado en blockchain. Y en el camino, surgen algunas ideas sobre la resolución de los problemas aplicados de la comunidad blockchain. Uno de estos trucos de la vida: cómo determinar la dirección de un contrato inteligente antes de la implementación con CREATE2, hoy quiero compartir con ustedes bajo el corte.

imagen
El código de operación CREATE2 se agregó al hard fork de Constantinopla el 28 de febrero de este año. Como se indica en el EIP, este código de operación se introdujo principalmente para canales estatales. Sin embargo, lo usamos para resolver otro problema.

Hay usuarios en el intercambio con saldos. Debemos proporcionar a cada usuario una dirección de Ethereum a la que cualquiera pueda enviar tokens, reponiendo así su cuenta. Llamemos a estas direcciones "billeteras". Cuando los tokens llegan a las billeteras, debemos enviarlos a una sola billetera (hotwallet).

En las siguientes secciones, analizo soluciones a este problema sin CREATE2 y explico por qué las abandonamos. Si solo está interesado en el resultado final, puede encontrarlo en la sección "Solución final".

Direcciones de Ethereum


La solución más fácil es generar nuevas direcciones de ethereum para nuevos usuarios. Estas direcciones serán billeteras. Para transferir tokens de la billetera a hotwallet, debe firmar la transacción llamando a la función transfer () con la clave privada de la billetera desde el backend.

Este enfoque tiene las siguientes ventajas:

  • es solo
  • El costo de transferir tokens de la billetera a Hotwallet es igual al precio de llamar a la función transfer ()

Sin embargo, abandonamos este enfoque porque tiene un inconveniente importante: debe almacenar claves privadas en algún lugar. Y el punto no es solo que se pueden perder, sino también que necesita controlar cuidadosamente el acceso a estas claves. Si al menos uno de ellos está comprometido, entonces los tokens de un determinado usuario no llegarán a una billetera activa.

imagen

Cree un contrato inteligente separado para cada usuario


La implementación de un contrato inteligente por separado para cada usuario elimina la necesidad de almacenar claves de billetera privadas en el servidor. El intercambio llamará a este contrato inteligente para transferir tokens al hotwallet.

También rechazamos esta decisión, ya que al usuario no se le puede mostrar la dirección de su billetera sin implementar un contrato inteligente (esto es realmente posible, pero de una manera bastante complicada con otras deficiencias que no discutiremos aquí). En el intercambio, un usuario puede crear tantas cuentas como necesite, y todos necesitan su propia billetera. Esto significa que debemos gastar dinero en la implementación del contrato, sin estar seguros de que el usuario usará esta cuenta.

Opcode CREATE2


Para solucionar el problema del método anterior, decidimos usar el código de operación CREATE2. CREATE2 le permite predeterminar la dirección en la que se implementará el contrato inteligente. La dirección se calcula utilizando la siguiente fórmula:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:] 

donde:
  • dirección : la dirección del contrato inteligente que llamará CREATE2
  • sal - valor aleatorio
  • init_code : código de bytes de contrato inteligente para la implementación

Esto garantiza que la dirección que proporcionamos al usuario contendrá el bytecode deseado. Además, este contrato inteligente se puede implementar cuando lo necesitemos. Por ejemplo, cuando un usuario decide usar su billetera por primera vez.
imagen
Además, puede calcular la dirección de un contrato inteligente cada vez en lugar de almacenarlo, ya que:

  • La dirección en la fórmula es constante, ya que esta es la dirección de nuestra fábrica de billeteras
  • salt - hash de user_id
  • init_code es persistente ya que usamos la misma billetera

Más mejoras


La solución anterior todavía tiene un inconveniente: debe pagar para implementar un contrato inteligente. Sin embargo, puedes deshacerte de esto. Para hacer esto, puede llamar a la función transfer () y luego selfdestruct () en el constructor de billetera. Y luego se devolverá el gas para el despliegue del contrato inteligente.

Contrariamente a la creencia popular, puede implementar un contrato inteligente en la misma dirección varias veces con el código de operación CREATE2. Esto se debe a que CREATE2 comprueba que el nonce de la dirección de destino es cero (se le asigna el valor "1" al comienzo del constructor). Al mismo tiempo, la función selfdestruct () restablece las direcciones nonce cada vez. Por lo tanto, si vuelve a llamar a CREATE2 con los mismos argumentos, se pasará la comprobación de nonce.

Tenga en cuenta que esta solución es similar a la opción de dirección ethereum, pero sin la necesidad de almacenar claves privadas. El costo de transferir dinero de una billetera a una billetera es aproximadamente igual al costo de llamar a la función transfer () , ya que no pagamos el despliegue de un contrato inteligente.

Decisión final


imagen

Inicialmente preparado por:

  • función para obtener sal por user_id
  • un contrato inteligente que llamará al código de operación CREATE2 con la sal adecuada (es decir, fábrica de billetera)
  • Código de bytes de cartera correspondiente al contrato con el siguiente constructor:

 constructor () { address hotWallet = 0x…; address token = 0x…; token.transfer (hotWallet, token.balanceOf (address (this))); selfdestruct (address (0)); } 

Para cada nuevo usuario, mostramos su dirección de billetera calculando

 keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:] 

Cuando un usuario transfiere tokens a la dirección de billetera correspondiente, nuestro servidor ve el evento Transferir con el parámetro _to igual a la dirección de billetera. En este punto, ya es posible aumentar el saldo del usuario en el intercambio antes de implementar la billetera.

Cuando se acumula un número suficiente de tokens en la dirección de la billetera, podemos transferirlos todos a la vez a una billetera activa. Para hacer esto, el backend llama a la función de la fábrica de contratos inteligentes, que realiza las siguientes acciones:

 function deployWallet ( uint256) { bytes memory walletBytecode =…; // invoke CREATE2 with wallet bytecode and salt } 

Por lo tanto, se llama al constructor del contrato de billetera inteligente, que transfiere todos sus tokens a la dirección de la billetera y luego se autodestruye.

El código completo se puede encontrar aquí . Tenga en cuenta que este no es nuestro código de producción, ya que decidimos optimizar el bytecode de la billetera y lo escribimos en los códigos de operación.

Publicado por Pavel Kondratenkov, especialista en Ethereum

Source: https://habr.com/ru/post/475496/


All Articles