Conceptos básicos de solidez (1)

Sitio web oficial de solidez

github

Soliditycurly-braces es un lenguaje de programación con llaves ( ) de tipo estático diseñado para desarrollar contratos inteligentes que se ejecutan en Ethereum ( ).Ethereum

La solidez crece rápidamente

Como idioma relativamente joven, Solidity está creciendo rápidamente. Nuestro objetivo es publicar lanzamientos regulares (ininterrumpidos) por mes y aproximadamente un lanzamiento de última hora por año. Puede realizar un seguimiento del estado de implementación de las nuevas funciones en el proyecto Solidity Github . Al developcambiar de la rama predeterminada ( ) a la rama de última hora , puede ver qué cambios se producirán en la próxima versión de última hora. Usted da forma activamente a Solidity brindando aportes y participando en el diseño del lenguaje .

1. Empezando ( v0.8.17)

Solidity es un lenguaje de alto nivel orientado a objetos que se utiliza para implementar contratos inteligentes. Los contratos inteligentes son programas que gestionan el comportamiento de las cuentas en el estado Ethereum.

Solidity es un lenguaje de llaves dirigido a la máquina virtual Ethereum (EVM). Está influenciado por C++, Python y JavaScript. Puedes encontrar más detalles sobre en qué idiomas se inspiró Solidity en la sección Influencias del idioma .

Solidity tiene tipos estáticos y admite herencia, bibliotecas y tipos complejos definidos por el usuario.

Con Solidity, puede crear contratos para fines como votación, financiación colectiva, subastas a ciegas y billeteras con múltiples firmas.

Al implementar su contrato, debe utilizar la última versión lanzada de Solidity. Salvo circunstancias especiales, sólo las últimas versiones recibirán parches de seguridad . Además, periódicamente se introducen cambios importantes y nuevas funciones. Actualmente utilizamos 0.y.znúmeros de versión para representar este rápido ritmo de cambio .

1.1 Comprender los conceptos básicos de los contratos inteligentes

Si no estás familiarizado con el concepto de contratos inteligentes, te recomendamos comenzar con la sección " Introducción a los Contratos Inteligentes ", que incluye:

1.2 Entendiendo la Solidez

Una vez que esté familiarizado con los conceptos básicos, le recomendamos leer las secciones Solidez con el ejemplo y "Descripción del idioma" para comprender los conceptos centrales del idioma.

1.3 Instalar el compilador Solidity

Hay varias formas de instalar el compilador Solidity, simplemente elija su opción preferida y siga los pasos enumerados en la página de instalación para instalarlo.

Puede probar ejemplos de código directamente en su navegador utilizando Remix IDE . Remix es un IDE basado en navegador web que le permite escribir, implementar y administrar contratos inteligentes de Solidity sin tener que instalar Solidity localmente.

Advertencia

Cuando la gente escribe software, puede tener errores. Al redactar contratos inteligentes, debe seguir las mejores prácticas de desarrollo de software establecidas. Esto incluye revisión de código, pruebas, auditoría y certificación de corrección. Los usuarios de contratos inteligentes a veces tienen más confianza en el código que sus autores, y las cadenas de bloques y los contratos inteligentes tienen sus propios problemas únicos que deben tener en cuenta, así que asegúrese de leer la sección Consideraciones de seguridad antes de usar el código de producción .

1.4 Más información

Si desea obtener más información sobre la creación de aplicaciones descentralizadas en Ethereum, los recursos para desarrolladores de Ethereum pueden ayudarlo a obtener más información sobre la documentación general de Ethereum, así como una amplia gama de tutoriales, herramientas y marcos de desarrollo.

Si tiene alguna pregunta, puede intentar buscar respuestas o preguntar en Ethereum StackExchange o en nuestro canal Gitter .

Índice de palabras clave

2. Introducción a los contratos inteligentes

2.1 Un contrato inteligente simple

Comencemos con un ejemplo básico que establece el valor de una variable y la expone a otros contratos para su acceso. Está bien si no lo entiendes ahora, te lo explicaremos en detalle más adelante.

2.1.1 Ejemplo de almacenamiento

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract SimpleStorage {
    
    
    uint storedData;

    function set(uint x) public {
    
    
        storedData = x;
    }

    function get() public view returns (uint) {
    
    
        return storedData;
    }
}

La primera línea le indica que el código fuente tiene licencia GPL versión 3.0. En la configuración predeterminada de publicación de código fuente, los especificadores de licencia legibles por máquina son muy importantes.

La siguiente línea especifica que el código fuente fue escrito para la versión 0.4.16 de Solidity, o una versión posterior de ese idioma, pero sin incluir la versión 0.9.0. Esto es para garantizar que el contrato no se pueda compilar con versiones nuevas (rotas) del compilador, donde puede comportarse de manera diferente. Los pragmas son instrucciones comunes para el compilador sobre cómo procesar el código fuente (por ejemplo pragma once).

En el sentido de Solidity, un contrato es una colección de código (its ) y datos (its ) ubicados en una dirección específica en la cadena de bloques Ethereumfunctionsstate . La línea uint storedData;declara una storedDatavariable de estado denominada, de tipo uint(entero sin signo de 256 bits). Puede considerarlo como una ranura ( ) en la base de datos slot que puede consultar y modificar llamando a funciones que administran la base de datos. En este caso, el contrato define funciones sety funciones que se pueden utilizar para modificar o recuperar el valor de una variable get.

Para acceder a los miembros del contrato actual (como statelas variables de estado ()), generalmente no es necesario agregar this.un prefijo; puede acceder a él directamente por su nombre. A diferencia de otros lenguajes, omitirlo no es sólo una cuestión de estilo, sino que da como resultado una forma completamente diferente de acceder a los miembros, pero hablaremos de eso más adelante.

Este contrato no tiene ninguna forma (factible) de impedirle publicar este número, excepto (debido a la infraestructura construida en Ethereum) permitir que cualquiera almacene un único número al que pueda acceder cualquier persona en el mundo. Cualquiera puede llamar sety sobrescribir su número nuevamente con un valor diferente, pero el número aún se almacena en el historial de la cadena de bloques. Más adelante verás cómo imponer restricciones de acceso para que sólo tú puedas cambiar los números.

advertir

Tenga cuidado al trabajar con texto Unicode, ya que los caracteres de apariencia similar (o incluso idénticos) pueden tener diferentes puntos de código y, por lo tanto, estar codificados en diferentes matrices de bytes.

Todos los identificadores (nombres de contratos, nombres de funciones y nombres de variables) están limitados al juego de caracteres ASCII. Los datos codificados en UTF-8 se pueden almacenar en variables de cadena.

2.1.2 Ejemplo de submoneda

El siguiente contrato implementa la forma más simple de criptomoneda. El contrato sólo permite a su creador crear nuevas monedas (posiblemente con diferentes esquemas de emisión). Cualquiera puede enviarse monedas entre sí sin registrar un nombre de usuario y contraseña, todo lo que necesita es un par de Ethereum.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract Coin {
    
    
    // The keyword "public" makes variables
    // accessible from other contracts
    address public minter;
    mapping (address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created
    constructor() {
    
    
        minter = msg.sender;
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator
    function mint(address receiver, uint amount) public {
    
    
        require(msg.sender == minter);
        balances[receiver] += amount;
    }

    // Errors allow you to provide information about
    // why an operation failed. They are returned
    // to the caller of the function.
    error InsufficientBalance(uint requested, uint available);

    // Sends an amount of existing coins
    // from any caller to an address
    function send(address receiver, uint amount) public {
    
    
        if (amount > balances[msg.sender])
            revert InsufficientBalance({
    
    
                requested: amount,
                available: balances[msg.sender]
            });

        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}

Este contrato introduce algunos conceptos nuevos, veámoslos uno por uno.

address public minter;Declarar una variable de estado de tipo dirección . addressEl tipo es un valor de 160 bits y no permite operaciones aritméticas . Se aplica a la dirección donde se almacena el contrato o al hash de la mitad pública de un par de claves que pertenece a una cuenta externa .

La palabra clave publicgenera automáticamente una función que le permite acceder al valor actual de la variable de estado desde fuera del contrato . Sin esta palabra clave, otros contratos no pueden acceder a esta variable. El código de función generado por el compilador es equivalente al siguiente (ignorando la suma por ahora external) view:

function minter() external view returns (address) {
    
     return minter; }

Podría agregar una función como la anterior, pero tendría una función y una variable de estado con el mismo nombre. No es necesario que haga esto, el compilador lo resolverá por usted.

La siguiente línea mapping (address => uint) public balances;también crea una variable de estado pública, pero es un tipo de datos más complejo. El tipo de asignación asigna direcciones a enteros sin signo .

Los mapas pueden considerarse como tablas hash que se inicializan virtualmente para que todas las claves posibles existan desde el principio y se asigne a un valor representado por una representación de bytes de todos ceros. Sin embargo, no es posible obtener una lista de todas las claves ni de todos los valores de un mapa. Documente lo que agregue al mapeo o utilícelo en un contexto no deseado. O mejor aún, mantenga una lista o utilice un tipo de datos más apropiado.

En el caso del mapeo, las funciones getterpublic creadas por palabras clave son más complejas. Como se muestra abajo:

function balances(address account) external view returns (uint) {
    
    
    return balances[account];
}

Admite consultar el saldo de una sola cuenta.
Esta línea event Sent(address from, address to, uint amount);declara un " evento " que se emite en la última línea de la funciónsend . Los clientes de Ethereum (como las aplicaciones web) pueden escuchar estos eventos emitidos en la cadena de bloques sin mucho costo. Una vez que se activa, el oyente recibe los parámetros y , lo que permite realizar un seguimiento de la transacción from.toamount

Para escuchar este evento, puede utilizar el siguiente código JavaScript, que utiliza web3.js para crear Coinel objeto de contrato y cualquier interfaz de usuario llama a la balances función generada automáticamente desde arriba:

Coin.Sent().watch({
    
    }, '', function(error, result) {
    
    
    if (!error) {
    
    
        console.log("Coin transfer: " + result.args.amount +
            " coins were sent from " + result.args.from +
            " to " + result.args.to + ".");
        console.log("Balances now:\n" +
            "Sender: " + Coin.balances.call(result.args.from) +
            "Receiver: " + Coin.balances.call(result.args.to));
    }
})

El constructor ( constructor) es una función especial que se ejecuta durante la creación del contrato y no se puede llamar más adelante. En este caso, almacena permanentemente la dirección de la persona que creó el contrato. msgLa variable (incluida txy block) es una variable global especial que contiene propiedades que permiten el acceso a la cadena de bloques. msg.senderSiempre la dirección de donde vino la llamada a la función actual (externa).

Las funciones que componen el contrato y las funciones que los usuarios y contratos pueden llamar son minty send.

mintLa función envía una cierta cantidad de monedas recién creadas a otra dirección. La llamada a la función require define una condición y, si no se cumple, revierte todos los cambios. En este caso, require(msg.sender == minter);asegúrese de que solo el creador del contrato pueda llamar mint. En términos generales, los creadores pueden acuñar tantos tokens como quieran, pero en algún momento esto dará lugar a un fenómeno conocido como "desbordamiento". Tenga en cuenta que debido a la aritmética marcada predeterminada , la transacción se revertirá si la expresión alances[receiver] += amount;se desborda, es decir, cuando la expresión en aritmética de precisión arbitraria es balances[receiver] + amountmayor que uint el valor máximo ( 2**256 - 1). Lo mismo ocurre con sendlas declaraciones dentro de funciones balances[receiver] += amount;.

Los errores le permiten brindarle a la persona que llama más información sobre una condición o por qué falló una operación. Error utilizado con la declaración de reversión . revertLa declaración aborta y revierte incondicionalmente requiretodos los cambios similares a una función, pero también le permite proporcionar nombres de error y otros datos que se proporcionarán a la persona que llama (y, en última instancia, a la aplicación front-end o al explorador de bloques) para facilitar la depuración o reaccionar ante los fracasos.

send Cualquier persona (que ya posea algunas de estas monedas) puede utilizar la función para enviar monedas a otras personas. Si el remitente no tiene suficientes monedas para enviar, if la condición se evalúa como verdadera. Como resultado, revert la operación provocará que falle y InsufficientBalancese proporcionarán detalles del error al remitente.

Aviso:

Si utiliza este contrato para enviar monedas a una dirección, cuando vea esa dirección en Blockchain Explorer no verá nada porque el registro de su envío de monedas y el saldo modificado solo se almacena aquí, en el almacenamiento de datos del contrato de monedas específico. . Al utilizar eventos, puede crear un "explorador de blockchain" que rastrea las transacciones y los saldos de nuevas monedas, pero debe verificar la dirección del contrato de las monedas, no la dirección del propietario de las monedas.

2.2 Conocimientos básicos de blockchain

Blockchain como concepto no es difícil de entender para los programadores. La razón es que las tecnologías más complejas (minería, hash, criptografía de curva elíptica, redes peer-to-peer, etc.) sólo están diseñadas para proporcionar a la plataforma un conjunto específico de características y promesas. Una vez que acepte estas características dadas, no tendrá que preocuparse por la tecnología subyacente, ¿o tendrá que saber cómo funciona AWS de Amazon internamente para usarla?

2.2.1 Transacción ( Transactions)

Blockchain es una base de datos de transacciones compartida globalmente. Esto significa que cualquiera puede leer las entradas de la base de datos participando en la red. Si desea cambiar algo en la base de datos, debe crear la llamada transacción ( transaction), que debe ser aceptada por todos los demás. La palabra transacción significa que los cambios que desea realizar (suponiendo que desee cambiar dos valores al mismo tiempo) no se realizan en absoluto o se aplican en absoluto. Además, cuando su transacción se aplica a la base de datos, ninguna otra transacción puede cambiarla.

Por ejemplo, suponga que tiene una tabla que enumera los saldos de todas las cuentas en una moneda electrónica. Si una solicitud pasa de una cuenta a otra, la naturaleza transaccional de la base de datos garantiza que si se resta un monto de una cuenta, siempre se agrega a la otra cuenta. Si por algún motivo no se puede agregar el monto a la cuenta de destino, la cuenta de origen no se modificará.

Además, las transacciones siempre están firmadas criptográficamente por el remitente (creador) . Esto simplifica la protección del acceso a modificaciones específicas de la base de datos. En el caso del dinero electrónico, un simple cheque puede garantizar que sólo la persona que posee la clave de la cuenta pueda transferir dinero desde ella.

2.2.2 Bloques

Un obstáculo importante que hay que superar es el llamado (en la terminología de Bitcoin) "ataque de doble gasto": ¿qué sucede si hay dos transacciones en la red y ambas quieren vaciar una cuenta? Sólo una transacción es válida, generalmente la primera aceptada. transacción. El problema es que "primero" no es un término objetivo en las redes peer-to-peer.

La respuesta abstracta a esto es que no tiene por qué importarle. Se elegirá para usted un orden de transacción aceptado globalmente, resolviendo así los conflictos. Las transacciones se agruparán en los llamados "bloques", que luego se ejecutarán y distribuirán a todos los nodos participantes. Si dos transacciones se contradicen, la transacción que termine siendo la segunda será rechazada y no pasará a formar parte del bloque.

Estos bloques forman una secuencia lineal en el tiempo, de ahí proviene la palabra "blockchain". Los bloques se agregan a la cadena a intervalos bastante regulares; en el caso de Ethereum, esto es aproximadamente cada 17 segundos.

Como parte del "mecanismo de selección secuencial" (llamado "minería"), puede suceder que los bloques se restauren de vez en cuando, pero sólo en la "punta" de la cadena. Cuantos más bloques se agreguen en un bloque en particular, es menos probable que ese bloque se recupere. Por lo tanto, es posible que su transacción sea restaurada o incluso eliminada de la cadena de bloques, pero cuanto más espere, menos probable será que esto suceda.

Aviso

No se garantiza que una transacción se incluya en el siguiente bloque o en cualquier bloque futuro específico porque no lo determina el remitente de la transacción, sino los mineros que deciden qué bloque contiene la transacción.

Si desea programar llamadas para contratos futuros, puede utilizar herramientas de automatización de contratos inteligentes o servicios de Oracle.

2.3 Máquina virtual Ethereum (EVM)

2.3.1 Descripción general

La máquina virtual Ethereum (EVM) es el entorno de ejecución para los contratos inteligentes de Ethereum . No solo está protegido, sino que en realidad está completamente aislado, lo que significa que el código que se ejecuta en EVM no puede acceder a la red, al sistema de archivos ni a otros procesos . Los contratos inteligentes pueden incluso tener acceso limitado a otros contratos inteligentes .

2.3.2 Cuentas

Hay dos tipos de cuentas en Ethereum que comparten el mismo espacio de direcciones: cuentas externas controladas por pares de claves pública-privada (es decir, personas) y cuentas de contrato controladas por un código almacenado con la cuenta.

La dirección de una cuenta externa está determinada por una clave pública, mientras que la dirección de un contrato se determina cuando se crea el contrato (se deriva de la dirección del creador y el número de transacciones enviadas desde esa dirección, el llamado "nonce ").

Independientemente de si la cuenta almacena código o no, EVM trata ambos tipos por igual.

Cada cuenta tiene un almacén de valores clave persistente que asigna palabras de 256 bits a palabras de 256 bits, llamado almacenamiento ( storage).

Además, cada cuenta tiene un Ether saldo de Ether ( para ser más precisos balance ) que se puede modificar enviando una transacción que contenga Ether.Wei1 ether10**18 wei

2.3.3 Transacciones

Una transacción es un mensaje enviado de una cuenta a otra (que puede ser el mismo o estar vacío, ver más abajo). Puede incluir datos binarios (llamados "carga útil( payload)") y Ether.

Si la cuenta de destino contiene código, el código se ejecuta y la carga útil se proporciona como datos de entrada.

Si no se establece ninguna cuenta de destino (la transacción no tiene destinatario o el destinatario está vacío), la transacción creará un nuevo contrato. Como se mencionó anteriormente, la dirección de este contrato no es una dirección cero, sino una dirección derivada del remitente y la cantidad de transacciones (el "nonce") que envió. La carga útil de esta transacción de creación de contrato se trata como código de bytes EVM y se ejecuta. Los datos de salida de esta ejecución se almacenan permanentemente como el código del contrato. Esto significa que para crear un contrato, no envía el código real del contrato, sino un código que devuelve ese código cuando se ejecuta.

Cuando se crea el contrato, su código todavía está vacío. Por lo tanto, el contrato que se está construyendo no debe revocarse hasta que el constructor haya terminado de ejecutarlo.

2.3.4 Gas

En el momento de la creación, a cada transacción se le cobra una determinada cantidad gas, que gasdebe ser pagada por el iniciador de la transacción ( tx.origin). Cuando el EVM ejecuta transacciones, gasse agota gradualmente de acuerdo con reglas específicas. Si gasen algún momento se agota (es decir, será negativo), out-of-gasse activa una excepción que finaliza la ejecución y restaura todas las modificaciones al estado realizadas en el marco de llamada actual.

Este mecanismo incentiva el uso económico del tiempo de ejecución de EVM y también compensa a los ejecutores de EVM (es decir, mineros/partes interesadas) por su trabajo. Dado que cada bloque tiene una cantidad máxima de gas, también limita la cantidad de trabajo necesario para verificar el bloque.

gas price Es un valor establecido por el iniciador de la transacción, quien debe pagar al ejecutor de EVM por adelantado gas_price * gas. Si queda algo de gas después de la ejecución, se devuelve al iniciador de la transacción. En el caso excepcional de revertir cambios, no se reembolsa el gas consumido.

Dado que los ejecutores de EVM pueden optar por incluir o no transacciones, los remitentes de las transacciones no pueden gasabusar del sistema estableciendo precios bajos.

2.3.5 Almacenamiento, memoria y pila

La máquina virtual Ethereum tiene tres áreas donde se pueden almacenar datos: almacenamiento, memoria y pila.

Cada cuenta tiene un storageárea de datos llamada almacenamiento() que es persistente entre llamadas a funciones y transacciones. El almacenamiento es un almacén de valores clave que asigna palabras de 256 bits a palabras de 256 bits. Es imposible enumerar el almacenamiento a partir de un contrato, el costo de leer el almacenamiento es relativamente alto y el costo de inicializar y modificar el almacenamiento es aún mayor. Debido a este costo, debe minimizar lo que se almacena en el almacenamiento persistente para satisfacer las necesidades de la operación del contrato. Almacene datos como cálculos derivados, almacenamiento en caché y agregación fuera del contrato. Un contrato no puede leer ni escribir en ningún almacenamiento que no sea el suyo.

La segunda área de datos se llama memoria ( memory), donde el contrato obtiene una instancia recién borrada para cada llamada de mensaje. La memoria es lineal y puede direccionarse a nivel de bytes, pero las lecturas están limitadas a 256 bits de ancho, mientras que las escrituras pueden tener 8 o 256 bits de ancho. Cuando se accede (lee o escribe) a una palabra de memoria no tocada previamente (es decir, cualquier desplazamiento dentro de la palabra), la memoria se amplía en una palabra (256 bits). A medida que te expandes, tienes que pagar por el gas natural. Cuanto más crece la memoria, mayor es el costo (su proporción es 2x).

El EVM no es una máquina de registro sino una máquina de pila, por lo que todos los cálculos stackse realizan en un área de datos llamada pila (). Su tamaño máximo es de 1024 elementos y contiene palabras de 256 bits. El acceso a la pila está restringido a la parte superior de la siguiente manera: uno de los 16 elementos superiores se puede copiar a la parte superior de la pila, o el elemento superior se puede intercambiar con uno de los 16 elementos debajo de él. Todas las demás operaciones toman los dos elementos superiores (o uno o más, según la operación) de la pila y empujan el resultado a la pila. Por supuesto, para acceder más profundamente a la pila, se pueden mover elementos de la pila al almacenamiento o la memoria, pero no es posible acceder simplemente a elementos arbitrarios más profundos de la pila sin eliminar primero la parte superior de la pila.

2.3.6 Conjunto de instrucciones

El conjunto de instrucciones del EVM se mantiene al mínimo para evitar implementaciones incorrectas o inconsistentes que podrían generar problemas de consenso. Todas las instrucciones operan con tipos de datos básicos, palabras de 256 bits o segmentos de memoria (u otras matrices de bytes). Están disponibles las operaciones aritméticas, bit a bit, lógicas y de comparación habituales. Son posibles saltos condicionales e incondicionales. Además, el contrato tiene acceso a propiedades relevantes del bloque actual, como su número y marca de tiempo.

Para obtener una lista completa, consulte la lista de códigos de operación que forma parte de la documentación del ensamblador en línea .

2.3.7 Llamadas de mensajes

Los contratos pueden llamar a otros contratos o enviar Ether a cuentas sin contrato mediante llamadas de mensajes. Las llamadas de mensajes son similares a las transacciones en el sentido de que tienen un origen, un destino, una carga útil de datos, éter, gas y datos de retorno. De hecho, cada transacción consta de una llamada de mensaje de nivel superior, y esta llamada de mensaje de nivel superior puede crear más llamadas de mensaje .

El contrato puede decidir gas cuánto del resto debe enviarse con la llamada de mensaje interno y cuánto desea retener. Si ocurre una excepción (o cualquier otra excepción) dentro de la llamada interna out-of-gas, se señalará mediante el valor de error en la pila. En este caso sólo se agota el gas enviado con la llamada. En Solidity, llamar al contrato en este caso provoca una excepción manual de forma predeterminada, por lo que la excepción "hace burbujear" en la pila de llamadas.

Como se mencionó anteriormente, el contrato llamado (que puede ser el mismo que la persona que llama) recibirá una instancia de memoria recién borrada y tendrá acceso a la carga útil de la llamada; la carga útil de la llamada estará disponible en un área llamada separada calldata. Una vez completada la ejecución, puede devolver datos, que se almacenarán en algún lugar de la memoria de la persona que llama, preasignada por esta. Todas estas llamadas son totalmente sincrónicas.

La profundidad de las llamadas está limitada a 1024, lo que significa que para operaciones más complejas, se deben utilizar bucles en lugar de llamadas recursivas. Además, en la llamada de mensajes sólo se puede transmitir el 63/64 del gas, lo que en la práctica da como resultado un límite de profundidad de algo menos de 1000.

2.3.8 Llamada a delegados y bibliotecas

Existe una variante especial de llamada de mensaje llamada , que es igual que la llamada de mensaje delegatecallexcepto que el código de la dirección de destino se ejecuta en el contexto del contrato de llamada y msg.senderno cambia su valor.msg.value

Esto significa que el contrato puede cargar código dinámicamente desde diferentes direcciones en tiempo de ejecución. El almacenamiento, la dirección actual y el saldo aún se refieren al contrato de llamada, solo que el código se toma de la dirección llamada.

Esto hace posible implementar la funcionalidad "biblioteca()" en Solidity library: código de biblioteca reutilizable que se puede aplicar al almacenamiento del contrato, por ejemplo, para implementar estructuras de datos complejas.

2.3.9 Registros

Los datos se pueden almacenar en una estructura de datos de índice especial que se asigna hasta el nivel de bloque. logs Solidity utiliza esta llamada característica para implementar eventos . Los contratos no pueden acceder a los datos de registro una vez creados, pero se puede acceder a ellos de manera efectiva desde fuera de la cadena de bloques. Debido a que partes de los datos de registro se almacenan en filtros de floración, estos datos se pueden buscar de una manera eficiente y criptográficamente segura, de modo que los pares de la red que no hayan descargado toda la cadena de bloques (los llamados "clientes ligeros") aún puedan encontrar estos registros. .

2.3.10 Crear

Los contratos pueden incluso crear otros contratos utilizando códigos de operación especiales (por ejemplo, no llaman simplemente a la dirección cero como lo hace una transacción). La única diferencia entre estas llamadas de creación ( create calls ) y las llamadas de mensajes normales es que los datos de la carga útil se ejecutan, los resultados se almacenan como código y la persona que llama/creador recibe la dirección del nuevo contrato en la pila.

2.3.11 Desactivar y Autodestrucción

La única forma de eliminar el código de la cadena de bloques es cuando el contrato para esa dirección realiza selfdestruct una acción. El Ether restante almacenado en esta dirección se envía al destino especificado y luego el almacenamiento y el código se eliminan del estado. En teoría, eliminar un contrato parece una buena idea, pero tiene peligros potenciales, como si alguien envía éter al contrato eliminado, el éter se pierde para siempre.

Incluso si se selfdestructelimina un contrato, sigue siendo parte del historial de la cadena de bloques y puede ser retenido por la mayoría de los nodos de Ethereum. Entonces utilizar la autodestrucción no es lo mismo que borrar datos del disco duro.

Incluso si el código del contrato no contiene la llamada selfdestruct, aún puede usar delegatecallo callcoderealizar la acción.

Si desea desactivar sus contratos, debe desactivarlos cambiando algún estado interno, lo que hará que se restablezcan todas las funciones. Esto hace que sea imposible utilizar el contrato ya que devuelve éter inmediatamente.

2.2.12 Contratos precompilados

Hay un pequeño conjunto de direcciones de contrato que son especiales: el rango de direcciones entre 1 y (inclusive) 8 contiene "contratos precompilados" que pueden denominarse cualquier otro contrato, pero su comportamiento (y por lo tanto su consumo de gas) no es almacenado por The El código EVM en esa dirección está definido (no contienen código), pero se implementa en el entorno de ejecución de EVM.

Diferentes cadenas compatibles con EVM pueden utilizar diferentes conjuntos de contratos precompilados. También es posible que se agreguen nuevos contratos precompilados a la cadena principal de Ethereum en el futuro, pero es razonable esperar que siempre estén en el rango de 1( e incluyendo).0xffff

3. Instale el compilador Solidity

3.1 Versiones

El versionado sólido sigue al versionado semántico . 0.x.yAdemás, las versiones de nivel de parche con la versión principal 0 (por ejemplo , ) no contendrán cambios importantes. Esto significa utilizar la versión 0.x.y del código compilado. Se puede 0.x.z compilar usando (donde z > y).

Además de las versiones de lanzamiento, también las proporcionamos nightly development buildspara que sea más fácil para los desarrolladores probar las próximas funciones y brindar comentarios tempranos. Sin embargo, tenga en cuenta que, si bien las compilaciones nocturnas son generalmente muy estables, contienen código de vanguardia de las ramas de desarrollo y no se garantiza que funcionen siempre. A pesar de nuestros mejores esfuerzos, pueden contener cambios no documentados y/o incompletos que no formarán parte de la versión real. No están destinados a la producción .

Al implementar su contrato, debe utilizar la última versión lanzada de Solidity . Esto se debe a que periódicamente se introducen cambios importantes, así como nuevas funciones y correcciones de errores. Ahora utilizamos 0.x números de versión para representar este rápido ritmo de cambio .

3.2 Remezcla

Para contratos pequeños y aprender Solidity rápidamente, recomendamos Remix.
Acceda a Remix en línea , si desea usarlo sin conexión a Internet, visite https://github.com/ethereum/remix-live/tree/gh-pages y descargue el archivo explicado en la página .zip. Remix también es una opción conveniente para probar compilaciones nocturnas sin tener que instalar varias versiones de Solidity.

Las otras opciones en esta página detallan la instalación del software compilador Solidity de línea de comandos en su computadora. Si está trabajando con un contrato más grande o necesita más opciones de compilación, elija el compilador de línea de comando.

4. Ejemplo de contrato de solidez

4.1 Contrato de votación

El contrato a continuación es bastante complejo, pero demuestra muchas de las características de Solidity. Implementa un contrato de votación. Por supuesto, los principales problemas del voto electrónico son cómo asignar el derecho de voto a las personas adecuadas y cómo evitar la manipulación. No resolveremos todos los problemas aquí, pero al menos mostraremos cómo realizar la votación delegada de modo que se logre el recuento automático de votos y una transparencia total al mismo tiempo.

La idea es crear un contrato para cada voto, dándole a cada opción un nombre corto. El creador del contrato, que es el presidente, otorgará a cada dirección derechos de voto individuales.

Las personas detrás de estas direcciones pueden optar por votar ellas mismas o delegar sus votos en alguien de su confianza.

Al final del periodo de votación winningProposal()se devolverá la propuesta con mayor número de votos.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/// @title Voting with delegation.
contract Ballot {
    
    
    // This declares a new complex type which will
    // be used for variables later.
    // It will represent a single voter.
    struct Voter {
    
    
        uint weight; // weight is accumulated by delegation
        bool voted;  // if true, that person already voted
        address delegate; // person delegated to
        uint vote;   // index of the voted proposal
    }

    // This is a type for a single proposal.
    struct Proposal {
    
    
        bytes32 name;   // short name (up to 32 bytes)
        uint voteCount; // number of accumulated votes
    }

    address public chairperson;

    // This declares a state variable that
    // stores a `Voter` struct for each possible address.
    mapping(address => Voter) public voters;

    // A dynamically-sized array of `Proposal` structs.
    Proposal[] public proposals;

    /// Create a new ballot to choose one of `proposalNames`.
    constructor(bytes32[] memory proposalNames) {
    
    
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        // For each of the provided proposal names,
        // create a new proposal object and add it
        // to the end of the array.
        for (uint i = 0; i < proposalNames.length; i++) {
    
    
            // `Proposal({...})` creates a temporary
            // Proposal object and `proposals.push(...)`
            // appends it to the end of `proposals`.
            proposals.push(Proposal({
    
    
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // Give `voter` the right to vote on this ballot.
    // May only be called by `chairperson`.
    function giveRightToVote(address voter) external {
    
    
        // If the first argument of `require` evaluates
        // to `false`, execution terminates and all
        // changes to the state and to Ether balances
        // are reverted.
        // This used to consume all gas in old EVM versions, but
        // not anymore.
        // It is often a good idea to use `require` to check if
        // functions are called correctly.
        // As a second argument, you can also provide an
        // explanation about what went wrong.
        require(
            msg.sender == chairperson,
            "Only chairperson can give right to vote."
        );
        require(
            !voters[voter].voted,
            "The voter already voted."
        );
        require(voters[voter].weight == 0);
        voters[voter].weight = 1;
    }

    /// Delegate your vote to the voter `to`.
    function delegate(address to) external {
    
    
        // assigns reference
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "You have no right to vote");
        require(!sender.voted, "You already voted.");

        require(to != msg.sender, "Self-delegation is disallowed.");

        // Forward the delegation as long as
        // `to` also delegated.
        // In general, such loops are very dangerous,
        // because if they run too long, they might
        // need more gas than is available in a block.
        // In this case, the delegation will not be executed,
        // but in other situations, such loops might
        // cause a contract to get "stuck" completely.
        while (voters[to].delegate != address(0)) {
    
    
            to = voters[to].delegate;

            // We found a loop in the delegation, not allowed.
            require(to != msg.sender, "Found loop in delegation.");
        }

        Voter storage delegate_ = voters[to];

        // Voters cannot delegate to accounts that cannot vote.
        require(delegate_.weight >= 1);

        // Since `sender` is a reference, this
        // modifies `voters[msg.sender]`.
        sender.voted = true;
        sender.delegate = to;

        if (delegate_.voted) {
    
    
            // If the delegate already voted,
            // directly add to the number of votes
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
    
    
            // If the delegate did not vote yet,
            // add to her weight.
            delegate_.weight += sender.weight;
        }
    }

    /// Give your vote (including votes delegated to you)
    /// to proposal `proposals[proposal].name`.
    function vote(uint proposal) external {
    
    
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "Has no right to vote");
        require(!sender.voted, "Already voted.");
        sender.voted = true;
        sender.vote = proposal;

        // If `proposal` is out of the range of the array,
        // this will throw automatically and revert all
        // changes.
        proposals[proposal].voteCount += sender.weight;
    }

    /// @dev Computes the winning proposal taking all
    /// previous votes into account.
    function winningProposal() public view
            returns (uint winningProposal_)
    {
    
    
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
    
    
            if (proposals[p].voteCount > winningVoteCount) {
    
    
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }

    // Calls winningProposal() function to get the index
    // of the winner contained in the proposals array and then
    // returns the name of the winner
    function winnerName() external view
            returns (bytes32 winnerName_)
    {
    
    
        winnerName_ = proposals[winningProposal()].name;
    }
}

posibles mejoras

Actualmente, se requieren muchas transacciones para distribuir los derechos de voto a todos los participantes. Además, si dos o más propuestas tienen el mismo número de votos, winningProposal()no se registrarán como empate. ¿Se te ocurren soluciones a estos problemas?

Supongo que te gusta

Origin blog.csdn.net/chinusyan/article/details/128334104
Recomendado
Clasificación