Solidity¶
Solidity es un lenguaje orientado a Contratos, de alto nivel, cuya sintaxis es similar a la de JavaScript y está diseñada para orientarse a la Máquina Virtual Ethereum (EVM).
Solidity está tipificado estáticamente, admite herencia, bibliotecas y tipos definidos por el usuario complejos entre otras características.
Como verá, es posible crear contratos para la votación, crowdfunding, subastas ciegas, carteras multi-firma y más.
Nota
La mejor manera de probar Solidity ahora es usando Remix (puede tardar un tiempo en cargar, por favor, tenga paciencia).
Enlaces Útiles¶
Integraciones de Solidity disponibles¶
- Remix
- IDE basado en el navegador con compilador integrado y entorno de ejecución de Solidity sin componentes del lado del servidor.
- Plugin IntelliJ IDEA
- Solidity plugin para IntelliJ IDEA (y todos los demás IDEs de JetBrains)
- Extensión de Visual Studio
- Solidity plugin para Microsoft Visual Studio que incluye el compilador Solidity.
- Package for SublimeText - Sintaxis del lenguaje de Solidity
- Destacado de la sintaxis de Solidity para el editor SublimeText.
- Etheratom
- Plugin para el editor de Atom que cuenta con resaltado de sintaxis, compilación y un entorno de tiempo de ejecución (nodo Backend & VM compatible).
- Atom Solidity Linter
- Plugin para el editor de Atom que proporciona linting de Solidity..
- Atom Solium Linter
- Ultimo de Solidty configurable para Atom usando Solium como base.
- Solium
- Un linter de línea de comandos para Solidity que sigue estrictamente las reglas prescritas por la Guía de Estilo de Solidity..
- Extensión de Visual Studio Code
- Solidity plugin para Microsoft Visual Studio Code que incluye resaltado de sintaxis y el compilador Solidity.
- Emacs Solidity
- Plugin para el editor de Emacs que proporciona resaltado de sintaxis y reportes de errores de compilación.
- Vim Solidity
- Plugin para el editor de Vim que proporciona resaltado de sintaxis.
- Vim Syntastic
- Plugin para el editor de Vim que proporciona comprobación de compilación.
Interrumpido:
- Mix IDE
- IDE basado en Qt para diseñar, depurar y probar contratos sólidos de inteligencia.
- Ethereum Studio
- IDE web especializado que también proporciona acceso a shell a un entorno Ethereum completo.
Herramientas de Solidity¶
- Dapp
- Herramienta de creación, gestor de paquetes y asistente de despliegue de Solidity.
- Solidity REPL
- Intente Solidity de forma instantánea con una consola Solidity de línea de comandos.
- solgraph
- Visualice el flujo de control de Solidity y resalte posibles vulnerabilidades de seguridad.
- evmdis
- EVM Disassembler que realiza análisis estáticos en el bytecode para proporcionar un nivel de abstracción más alto que las operaciones raw de EVM.
- Doxity
- Generador de documentación para Solidity.
Analizadores de Solidity de Terceros y Gramática¶
- solidity-analista
- Analista de Solidity para JavaScript
- Gramática de Solidity para ANTLR 4
- Gramática de solidity para el generador analista ANTLR 4
Documentación del Lenguaje de Programacion Solidity¶
En las páginas siguientes, veremos primero un contrato inteligente simple escrito en Solidity seguido de los conceptos básicos sobre blockchains y la máquina virtual Ethereum..
La siguiente sección explicará varias características de Solidity dando útiles ejemplos de contratos Recuerde que siempre puede probar los contratos en su navegador !
La última y más extensa sección cubrirá todos los aspectos de la Solidity en profundidad.
Si aún tiene preguntas, puede intentar buscar o preguntar en el sitio Ethereum Stackexchange , o visitar nuestro canal gitter . Ideas para mejorar la solidity o esta documentación son siempre bienvenidas!
Véase también la versión en ruso (русский перевод) ..
Contenido¶
Índice de palabras clave, Página de búsqueda
Introducción a los contratos inteligentes¶
Un simple contrato inteligente¶
Comencemos con el ejemplo más básico. Está bien si no entiendes todo en este momento, entraremos en más detalles más adelante.
Almacenamiento¶
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData;
function set(uint x) {
storedData = x;
}
function get() constant returns (uint) {
return storedData;
}
}
La primera línea indica simplemente que el código fuente está escrito para Solidity versión 0.4.0 o cualquier cosa más nueva que no rompa la funcionalidad (hasta, pero no incluyendo, la versión 0.5.0). Esto es para asegurarse de que el contrato no se comporta repentinamente de manera diferente con una nueva versión del compilador. La palabra clave pragma se llama así porque, en general, los pragmas son instrucciones para el compilador sobre cómo tratar el código fuente (por ejemplo, pragma una vez).
Un contrato en el sentido de Solidity es una colección de código (sus funcciones) y
datos (su estado) que reside en una dirección específica en la cadena de bloques Ethereum.
La línea uint storedData;
declara una variable de estado llamada storedData
de tipo
uint
(entero sin signo de 256 bits).
Se puede pensar en ella como una sola ranura en una base de datos que puede ser consultada y alterada por las funciones de llamada del código
que gestiona la base de datos. En el caso de Ethereum, éste es siempre el contrato propietario.
Y en este caso, las funciones set
y get
se pueden utilizar para modificar o recuperar el valor de la variable.
Para acceder a una variable de estado, no necesitas el prefijo this.
como es común en otros lenguajess.
Este contrato no hace mucho todavía (debido a la infraestructura construida por Ethereum) aparte de permitir que cualquier persona almacene un solo número
que sea accesible por cualquier persona en el mundo sin una manera (posible) de prevenirle publicar este número.
Por supuesto, cualquier persona podría apenas llamar set
otra vez con un valor diferente
y sobrescribir su número, pero el número todavía será almacenado en la historia de la cadena de bloque. Más adelante, veremos cómo puede imponer restricciones
de acceso para que solo usted pueda alterar el número.
Ejemplo de subcuenta¶
El siguiente contrato implementará la forma más simple de una criptomoneda. Es posible generar monedas fuera del aire, pero sólo la persona que creó el contrato será capaz de hacer eso (es trivial implementar un esquema de emisión diferente). Además, cualquier persona puede enviar monedas entre sí sin necesidad de registrarse con nombre de usuario y contraseña - todo lo que necesita es un par de comandos en Ethereum.
pragma solidity ^0.4.0;
contract Coin {
// The keyword "public" makes those variables
// readable from outside.
address public minter;
mapping (address => uint) public balances;
// Events allow light clients to react on
// changes efficiently.
event Sent(address from, address to, uint amount);
// This is the constructor whose code is
// run only when the contract is created.
function Coin() {
minter = msg.sender;
}
function mint(address receiver, uint amount) {
if (msg.sender != minter) return;
balances[receiver] += amount;
}
function send(address receiver, uint amount) {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
}
}
Este contrato introduce algunos nuevos conceptos, vamos a pasar por ellos uno por uno.
La Linea address public minter;
declara una variable de estado de tipo address que es accesible al público. El address
tipo es un valor de 160 bits que no permite operaciones aritméticas. Es adecuado para almacenar direcciones de contratos o de teclas pertenecientes a personas externas.
La palabra clave public
genera automáticamente una función que
le permite acceder al valor actual de la variable de estado. Sin esta palabra clave, otros contratos no tienen forma de acceder a la variable.
La función se verá así:
function minter() returns (address) { return minter; }
Por supuesto, la adición de una función exactamente como esa no funcionará porque tendríamos una función y una variable de estado con el mismo nombre, pero esperemos que se obtenga la idea - de la salida de datos del compilador.
La línea siguiente, mapping (address
=> uint) public balances;
también crea una variable de estado público, pero es un tipo de datos más complejo. El tipo asigna direcciones a enteros sin signo.
Las asignaciones se pueden ver como tablas de hash
que son prácticamente inicializadas de tal manera que cada clave posible existe y se asigna a un valor cuya representación de bytes es todos ceros.
Esta analogía no va demasiado lejos, sin embargo, ya que no es posible obtener una lista de todas las claves de un mapeo, ni una lista de todos los valores.
Por lo tanto, tengalo en cuenta (o mejor, guarde una lista o utilice un tipo de datos más avanzado)
donde agregue la asignación o utilícelo en un contexto en el que no sea necesario, como éste.
La funcion getter creada por la palabra clave
public
es un poco más compleja en este caso. Se parece aproximadamente a lo siguiente:
function balances(address _account) returns (uint) {
return balances[_account];
}
Como puede ver, puede utilizar esta función para consultar fácilmente el saldo de una sola cuenta.
La línea event Sent(address from,
address to, uint amount);
declara un llamado “evento” que se dispara en la última línea de la función. send
.
Interfaces de usuario (así como aplicaciones de servidor, por supuesto) puede escuchar los eventos que se disparó en la cadena de bloque sin mucho costo.
Tan pronto como se dispara, el oyente también recibirá los argumentos,
from
, to
y
amount
, lo que hace que sea fácil de realizar un seguimiento de las transacciones.
Para escuchar este evento, usted usaría:
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));
}
})
Observe cómo la función automaticamente generada balances
es llamada desde la interfaz de usuario.
La función especial Coin
es el constructor que se ejecuta durante la creación del contrato y no se puede llamar
después. Almacena permanentemente la dirección de la persona que crea el contrato:
msg
(junto con tx
y
block
)
son una variable global mágica que contiene algunas propiedades que permiten el acceso a la Blockchain.
msg.sender
Es siempre la dirección de donde proviene la llamada de la función (externa) actual.
Finalmente, las funciones que en realidad terminan con el contrato y pueden ser llamadas por
usuarios y contratos por igual son mint
y send
.
Si mint
es llamado por cualquier persona excepto
la cuenta que creó el contrato, nada sucederá. Por otro lado,
send
puede ser utilizado por cualquier persona (que ya tiene algunas de estas monedas)
para enviar monedas a cualquier otra persona. Tenga en cuenta que si utiliza este contrato para
enviar monedas a una dirección, no verá nada cuando mire esa dirección en un explorador de
blockchain, por el hecho de que haya enviado monedas y los saldos modificados sólo se almacenan
en el almacenamiento de datos de este Contrato de monedas en particular.
Mediante el uso de eventos es relativamente fácil crear un “explorador blockchain ”
que realiza un seguimiento de transacciones y saldos de su nueva moneda.
Fundamentos de Blockchain¶
Blockchains como un concepto no son demasiado difíciles de entender para los programadores. La razón es que la mayoría de las complicaciones (minería, hashing, criptografía de curva elíptica, redes peer-to-peer , etc.) están ahí para proporcionar un cierto conjunto de características y promesas. Una vez que acepta estas características tal y como están dadas, no tiene que preocuparse por la tecnología subyacente, o ¿tiene que saber cómo funciona AWS de Amazon internamente para usarlo?
Transacciones¶
Una cadena de bloques es una base de datos transaccional globalmente compartida. Esto significa que todos pueden leer entradas en la base de datos simplemente participando en la red. Si desea cambiar algo en la base de datos, tiene que crear una llamada transacción que debe ser aceptada por todos los demás. La palabra transacción implica que el cambio que usted quiere hacer (asume que usted quiere cambiar dos valores al mismo tiempo) no se hace en absoluto o se aplica totalmente. Además, mientras la transacción se aplica a la base de datos, ninguna otra transacción puede alterarla.
Por ejemplo, imagine una tabla que enumera los saldos de todas las cuentas en una moneda electrónica. Si se solicita una transferencia de una cuenta a otra, la naturaleza transaccional de la base de datos garantiza que si se resta la cantidad de una cuenta, siempre se agrega a la otra. Si debido a cualquier motivo, agregar la cantidad a la cuenta de destino no es posible, la cuenta de origen tampoco se modifica.
Además, una transacción siempre es criptograficamente firmada por el remitente (creador). Esto hace que sea sencillo proteger el acceso a modificaciones específicas de la base de datos. En el ejemplo de la criptomoneda, un simple cheque asegura que sólo la persona que tiene las llaves de la cuenta puede transferir dinero de ella.
Bloques¶
Un obstáculo importante para superar es lo que, en términos de Bitcoin, se llama un “double-spend attack”(ataque de doble gasto): ¿Qué sucede si dos transacciones existen en la red que ambos quieren vaciar una cuenta, un llamado conflicto?
La respuesta abstracta a esto es que usted no tiene que preocuparse. Una orden de las transacciones será seleccionada para usted, las transacciones serán agrupadas en lo que se llama un "bloque" y luego serán ejecutadas y distribuidas entre todos los nodos participantes. Si dos transacciones se contradicen, la que termina siendo segunda será rechazada y no pasará a formar parte del bloque.
Estos bloques forman una secuencia lineal en el tiempo y es ahí donde deriva la palabra “blockchain” Los bloques se añaden a la cadena en intervalos regulares - para Ethereum esto es aproximadamente cada 17 segundos.
Como parte del “mecanismo de seleccion de ordenes” (como se le llama en “mineria”) puede suceder que los bloques se reviertan de vez en cuando, pero solo en la “punta” de la cadena. Por lo tanto, puede ser que sus transacciones sean revertidas e incluso eliminadas de la cadena de bloqueo, pero cuanto más tiempo espere, menos probable será.
La máquina virtual Ethereum¶
Visión de conjunto¶
La Ethereum Virtual Machine o EVM es el entorno de ejecución para contratos inteligentes en Ethereum. No sólo esta encajonado sino que está totalmente aislado, lo que significa que el código que se ejecuta dentro del EVM no tiene acceso a red, sistema de archivos u otros procesos. Los contratos inteligentes incluso tienen acceso limitado a otros contratos inteligentes.
Cuentas¶
Existen dos tipos de cuentas en Ethereum que comparten el mismo espacio de direcciones: Cuentas Externas que son controladas por pares de claves público-privadas (es decir, seres humanos) y Cuentas de contrato que están controladas por el código almacenado junto con la cuenta.
La dirección de una cuenta externa se determina a partir de la clave pública mientras que la dirección de un contrato se determina en el momento de la creación del contrato (se deriva de la dirección del creador y el número de transacciones enviadas desde esa dirección, “nonce”).
Independientemente de si la cuenta almacena o no el código, los dos tipos son tratados por igual por el EVM.
Cada cuenta tiene un almacén de valores clave persistente que asigna palabras de 256 bits a palabras de 256 bits llamadas storage (almacenamiento).
Además, cada cuenta tiene un balance en Ether (en “Wei” para ser exactos) que se puede modificar mediante el envío de transacciones que incluyen Ether.
Transacciones¶
Una transacción es un mensaje que se envía desde una cuenta a otra cuenta (que podría ser la misma o la cuenta cero especial, véase más adelante). Puede incluir datos binarios (su carga útil) y Ether.
Si la cuenta de destino contiene código, ese código se ejecuta y la carga útil se proporciona como datos de entrada.
Si la cuenta de destino es la cuenta cero (la cuenta con la dirección 0
),
la transacción crea un nuevo contrato. Como ya se ha mencionado, la dirección de ese contrato no es la dirección cero
sino una dirección derivada del remitente y su número de transacciones enviadas (el “nonce”).
La carga útil de dicha transacción de creación de contrato se toma
como bytecode EVM y se ejecuta. La salida de esta ejecución se almacena permanentemente como el código del contrato.
Esto significa que para crear un contrato, usted no envía el código real del contrato, pero en realidad el código devuelve ese código.
Gas¶
Al momento de la creación, cada transacción se carga con una cierta cantidad de gas, cuyo propósito es limitar la cantidad de trabajo que se necesita para ejecutar la transacción y pagar por esta ejecución. Mientras que el EVM ejecuta la transacción, el gas se agota gradualmente según reglas específicas.
El precio del gas es un valor establecido por el creador de la transacción, que tiene que pagar por adelantado
gas_price * gas
de la cuenta de envío.
Si queda algo de gas después de la ejecución, se devuelve de la misma manera.
Si el gas se agota en cualquier punto (es decir, es negativo), se dispara una excepción de out-of-gas, que revierte todas las modificaciones hechas al estado en el marco de llamada actual.
Almacenamiento, memoria y la pila¶
Cada cuenta tiene un área de memoria persistente que se denomina storage (almacenamiento). El almacenamiento es un almacén de valores clave que asigna palabras de 256 bits a palabras de 256 bits. No es posible enumerar el almacenamiento desde dentro de un contrato y es comparativamente costoso leer y aún más, modificar el almacenamiento. Un contrato no puede leer ni escribir en ningún almacenamiento aparte de los suyos.
La segunda área de memoria se llama memory, de la cual un contrato obtiene una instancia recién borrada para cada llamada de mensaje. La memoria es lineal y puede ser dirigida a nivel de byte, pero las lecturas están limitadas a un ancho de 256 bits, mientras que las escrituras pueden ser de 8 bits o 256 bits de ancho. La memoria se expande mediante una palabra (256 bits), al acceder (ya sea a la lectura oa la escritura) una palabra de memoria previamente intacta (es decir, cualquier desplazamiento dentro de una palabra). En el momento de la expansión, el costo en el gas debe ser pagado. La memoria es más costosa cuanto más crece (escalas cuadráticas).
El EVM no es una máquina de registro, sino una máquina de pila, por lo que todos los cálculos se realizan en un área llamada stack (la pila). Tiene un tamaño máximo de 1024 elementos y contiene palabras de 256 bits. El acceso a la pila se limita al extremo superior de la siguiente manera: Es posible copiar uno de los 16 elementos superiores en la parte superior de la pila o intercambiar el elemento superior con uno de los 16 elementos debajo de él. Todas las demás operaciones toman los dos elementos superiores (o uno, o más, dependiendo de la operación) de la pila y empujan el resultado a la pila. Por supuesto, es posible mover elementos de pila a almacenamiento o memoria, pero no es posible acceder a elementos arbitrarios más profundos en la pila sin primero quitar la parte superior de la pila.
Conjunto de instrucciones¶
El conjunto de instrucciones del EVM se mantiene mínimo para evitar implementaciones incorrectas que podrían causar problemas de consenso. Todas las instrucciones funcionan con el tipo de datos básico, palabras de 256 bits. Están presentes las operaciones aritméticas, de bits, lógicas y de comparación habituales. Saltos condicionales e incondicionales son posibles. Además, los contratos pueden acceder a las propiedades relevantes del bloque actual como su número y marca de tiempo (timestamp).
Message Calls¶
Los contratos pueden llamar a otros contratos o enviar Ether a cuentas no contractuales por medio de Message Calls. Las llamadas de mensajes son similares a las transacciones, ya que tienen una fuente, un destino, datos útiles, datos de éter, de gas y de retorno. De hecho, cada transacción consiste en una llamada de mensaje de nivel superior que a su vez puede crear más llamadas de mensajes.
Un contrato puede decidir cuánto de su gas restante debe ser enviado con la llamada interna del mensaje y cuánto quiere retener. Si se produce una excepción out-of-gas en la llamada interna (o en cualquier otra excepción), ésta será señalada por un valor de error puesto en la pila. En este caso, sólo se gasta el gas enviado junto con la llamada. En Solidity, el contrato de llamada provoca una excepción manual por defecto en tales situaciones, de modo que las excepciones “bubble up” la pila de llamadas.
Como ya se ha dicho, el contrato llamado (que puede ser el mismo que el que llama) recibirá una instancia recién borrada de memoria y tendrá acceso a la carga útil de la llamada, que se proporcionará en un área separada llamada calldata. Después de que haya terminado la ejecución, puede devolver datos que serán almacenados en una ubicación en la memoria de la llamada preasignada por la persona que llama.
Las llamadas están limited a una profundidad de 1024, lo que significa que para operaciones más complejas, los bucles deberían ser preferidos a las llamadas recursivas.
Delegatecall / Callcode and Libraries¶
Existe una variante especial de una llamada de mensaje, llamada delegatecall
que es idéntica a una llamada de mensaje aparte del hecho de que el código en la dirección de destino se ejecuta en el contexto del contrato de llamada y
msg.sender
y msg.value
no cambian sus valores.
Esto significa que un contrato puede cargar dinámicamente código desde una dirección diferente en tiempo de ejecución. Almacenamiento, dirección actual y Balance todavía se refieren al contrato de llamada, sólo el código se toma de la dirección llamada.
Esto hace posible implementar la función “library” en Solidity: El codigo reusable de library que se puede aplicar al almacenamiento de un contrato, por ejemplo para implementar una estructura de datos compleja.
Logs (Registros)¶
Es posible almacenar datos en una estructura de datos especialmente indexada que mapea todo el camino hasta el nivel de bloque. Esta característica llamada logs es utilizada por Solidity para implementar events. Los contratos no pueden acceder a los datos de registro después de haber sido creados, pero se puede acceder de forma eficiente desde fuera de la Blockchain. Dado que una parte de los datos de registro se almacena en bloom filters, es posible buscar estos datos de una manera eficiente y criptográficamente segura, por lo que los pares de la red que no descargan la blockchain completa (“light clients”) pueden encontrar estos registros .
Create¶
Los contratos pueden incluso crear otros contratos utilizando un código de operación especial (es decir, no llaman simplemente a la dirección cero). La única diferencia entre estas create calls y las llamadas de mensajes normales es que los datos de carga útil se ejecutan y el resultado se almacena como código y el llamador / creador (caller / creator) recibe la dirección del nuevo contrato en la pila.
Self-destruct (Auto destrucción)¶
La única posibilidad de que el código se elimine de la blockchain es cuando un contrato en esa dirección realiza la operacion
selfdestruct
.
El resto de Ether almacenado en esa dirección se envía a un destino designado y, a continuación, el almacenamiento y el código se quita del estado.
Advertencia
Incluso si el código de un contrato no contiene una llamada selfdestruct
,
todavía puede realizar esa operación utilizando delegatecall
o callcode
.
Nota
La poda de contratos antiguos puede o no ser implementada por los clientes de Ethereum. Además, los nodos de archivo podrían optar por mantener el almacenamiento de contrato y el código indefinidamente.
Nota
Actualmente,las cuentas externas no se pueden eliminar del estado.
Instalación de Solidity¶
Versiones¶
Las versiones de Solidity siguen el versionado semántico y además de las versiones, también se ponen a disposición de nightly development builds(desarrollos nocturnos). Las construcciones nocturnas no están garantizadas para trabajar y, a pesar de los mejores esfuerzos, pueden contener cambios indocumentados y / o rotos. Recomendamos usar la última versión. Los instaladores de paquetes siguientes usarán la última versión.
Remix¶
Si sólo desea probar Solidity para pequeños contratos, puede probar Remix que no necesita ninguna instalación. Si desea utilizarlo sin conexión a Internet, puede ir a https://github.com/ethereum/browser-solidity/tree/gh-pages y descargar el archivo .ZIP como se explica en esa página.
npm / Node.js¶
Esta es probablemente la forma más portátil y más conveniente para instalar Solidity localmente.
Se proporciona una biblioteca de JavaScript independiente de la plataforma compilando la fuente C ++ en JavaScript usando Emscripten. Se puede utilizar en proyectos directamente (como Remix). Consulte el repositorio de solc-js para obtener instrucciones.
También contiene una herramienta de línea de comandos llamada solcjs, que se puede instalar a través de npm:
npm install -g solc
Nota
Las opciones comandline de solcjs no son compatibles con solc y herramientas (como geth) esperando que el comportamiento de solc no funcione con solcjs.
Docker¶
Proporcionamos versiones actualizadas para el compilador. El stable
repositorio contiene versiones liberadas mientras el nightly
repositorio contiene cambios potencialmente inestables en la rama de desarrollo.
docker run ethereum/solc:stable solc --version
En la actualidad, la imagen docker sólo contiene el compilador ejecutable, por lo que tiene que hacer algún trabajo adicional para vincular en los directorios de origen y salida.
Paquetes binarios¶
Paquetes binarios de Solidity disponibles en solidity/releases.
También tenemos PPA para Ubuntu. Para la última versión estable.
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc
Si desea utilizar la versión de desarrollo de vanguardia:
sudo add-apt-repository ppa:ethereum/ethereum
sudo add-apt-repository ppa:ethereum/ethereum-dev
sudo apt-get update
sudo apt-get install solc
También estamos lanzando un paquete rápido, que es instalable en todas las distribuciones Linux soportadas. Para instalar la última versión estable de solc:
sudo snap install solc
O si desea ayudar a probar el solc inestable con los cambios más recientes de la rama de desarrollo:
sudo snap install solc --edge
Arch Linux también tiene paquetes, aunque limitados a la última versión de desarrollo:
pacman -S solidity
Homebrew no tiene botellas pre-construidas en el momento de escribir, siguiendo una migración de Jenkins a TravisCI, pero Homebrew debería funcionar bien como un medio para construir desde la fuente. Volveremos a añadir las botellas pre-construidas pronto.
brew update
brew upgrade
brew tap ethereum/ethereum
brew install solidity
brew linkapps solidity
Si necesita una versión específica de Solidity, puede instalar una fórmula Homebrew directamente desde Github.
Ver solidity.rb commits en Github.
Siga los enlaces de historia hasta que tenga un vínculo de archivo sin formato de un commit específico de
solidity.rb
.
Instálelo usando brew
:
brew unlink
solidity
# Install 0.4.8
brew install https:
//raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
Gentoo Linux también proporciona un paquete de solidity que se puede instalar usando emerge
:
emerge dev
-lang/solidity
Construyendo desde la Fuente¶
Clonar el repositorio¶
Para clonar el código fuente, ejecute el siguiente comando:
git clone --recursive https://github.com/ethereum/solidity.git
cd solidity
Si quieres ayudar a desarrollar Solidity, debes hacer fork del repositorio Solidity y añadir tu fork personal como un segundo mando remoto:
cd solidity
git remote add personal git@github.com:[username]/solidity.git
Solidity tiene submodulos git. Asegúrese de que estén correctamente cargados:
git submodule update --init --recursive
Requisitos previos - macOS¶
Para macOS, asegúrese de tener instalada la última versión de Xcode. Este contiene el compilador Clang C++, el Xcode IDE y otras herramientas de desarrollo de Apple que son necesarias para crear aplicaciones C ++ en OS X. Si instala Xcode por primera vez o acaba de instalar una nueva versión, deberá aceptar La licencia antes de que pueda hacer compilaciones de línea de comandos:
sudo xcodebuild -license accept
Nuestras compilaciones de OS X requieren que instale el gestor de paquetes Homebrew para instalar dependencias externas. Aqui esta como desinstalar Homebrew, si alguna vez quieres empezar de nuevo desde cero.
Requisitos previos - Windows¶
Necesitará instalar las siguientes dependencias para las compilaciones de Windows de Solidity:
Software | Notas |
---|---|
Git for Windows | Herramienta de línea de comandos para recuperar fuentes de Github. |
CMake | Generador de archivos de compilación multiplataforma. |
Visual Studio 2015 | Compilador C ++ y entorno de desarrollo. |
Dependencias Externas¶
Ahora tenemos un script de “one button” que instala todas las dependencias externas requeridas en macOS, Windows y en numerosas distros de Linux. Esto solía ser un proceso manual de varios pasos, pero ahora se puede hacer en una sola linea de codigo:
./scripts/install_deps.sh
O, en Windows:
scripts\install_deps.bat
Compilación de línea de comandos¶
El proyecto de Solidity utiliza CMake para configurar la compilación. Construir Solidity es bastante similar en Linux, macOS y otras unidades:
mkdir build
cd build
cmake .. && make
o incluso mas facil:
#
note: this will install binaries solc and soltest at usr/local/bin
./scripts/build.sh
E incluso en Windows:
mkdir build
cd build
cmake -G "Visual Studio 14 2015 Win64" ..
Este último conjunto de instrucciones debería resultar en la creación de solidity.sln en ese directorio de compilación. Hacer doble clic en ese archivo debería provocar que Visual Studio se dispare. Sugerimos construir la configuración RelWithDebugInfo pero todos los demás funcionan.
Como alternativa, puede crear para Windows en la línea de comandos, así:
cmake --build . --config RelWithDebInfo
Opciones de CMake¶
Si está interesado en las opciones de CMake disponibles, ejecute cmake .. -LH
.
La cadena de versión en detalle¶
La cadena de versiones de Solidity contiene cuatro partes:
- El número de versión
- La etiqueta pre-liberación, por lo general ajustado a
develop.YYYY.MM.DD
onightly.YYYY.MM.DD
- Hacer commit en el formato de
commit.GITHASH
- La Plataforma tiene un número arbitrario de elementos, que contiene detalles sobre la plataforma y el compilador
Si hay modificaciones locales, el commit será fijado posteriormente con .mod
.
Estas piezas se combinan según lo requiera Semver, donde la etiqueta de pre-lanzamiento de Solidity es igual a la pre-lanzamiento de Semver y Solidity commit y la plataforma combinadas forman los metadatos de construcción de Semver.
Un ejemplo de salida: 0.4.8+commit.60cc1668.Emscripten.clang
.
Un ejemplo de pre-lanzamiento: 0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang
Información importante sobre el control de versiones¶
Después de realizarse un lanzamiento, es cambiado el nivel de versión del path , ya que suponemos que sólo siguen los cambios de nivel de parches(path).
Cuando se fusionan los cambios, la versión debe ser superada según semver y la magnitud del cambio.
Finalmente, los lanzamientos siempre se hacen con la versión recientemente construida, pero sin un pre-lanzamiento
.
Ejemplo:
- es realizado el lanzamiento 0.4.0
- recientemente se construye una versión 0.4.1
- cambios no bruscos son introducidos – no cambian la versión
- es introducido un cambio rompeador – la versión es elevada a 0.5.0
- el lanzamiento 0.5.0 es realizado
Esto sigue funcionando bien con la version pragma.
Solidity con Ejemplos¶
Votación¶
El siguiente contrato es bastante complejo, pero muestra muchas de las características de Solidity. Implementa un contrato de votación. Por supuesto, los principales problemas del voto electrónico es cómo asignar los derechos de voto a las personas correctas y cómo evitar la manipulación. No resolveremos todos los problemas aquí, pero al menos mostraremos cómo se puede hacer el voto delegado para que el conteo de votos sea automático y completamente transparente al mismo tiempo.
La idea es crear un contrato de votacion, proporcionando un nombre corto para cada opción. Entonces el creador del contrato que sirve como presidente dará el derecho de votar a cada dirección individualmente.
Las personas detrás de las direcciones pueden entonces elegir votar ellos mismos o delegar su voto a una persona en la que confían.
Al final del tiempo de votación, winningProposal()
devolverá la propuesta con el mayor número de votos.
pragma solidity ^0.4.11;
/// @title Votación con delegación.
contract Ballot {
// Esto declara un nuevo tipo complejo que
// se utilizará para las variables más adelante.
// Representará a un solo votante.
struct Voter {
uint weight; // weight se acumula por delegación
bool voted; // si es verdad, esa persona ya votó
address delegate; // persona delegada a
uint vote; // índice de la propuesta votada
}
// Este es un tipo para una propuesta(Caracteristicas de cada propuesta).
struct Proposal {
bytes32 name; // nombre abreviado (hasta 32 bytes)
uint voteCount; // número de votos acumulados
}
address public chairperson;
// Esto declara una variable de estado que
// almacena una estructura `Voter` para cada dirección(address) posible.
mapping(address => Voter) public voters;
// Una matriz(array) de tamaño dinámico de estructuras de 'Propuesta'.
Proposal[] public proposals;
/// Cree una nueva votacion para elegir uno de `proposalNames` (nombres propuestos).
function Ballot(bytes32[] proposalNames) {
chairperson = msg.sender;
voters[chairperson].weight = 1;
// Para cada uno de los nombres de propuesta proporcionados,
// crea un nuevo objeto de propuesta y agrega
// al final de la matriz(array).
for (uint i = 0; i < proposalNames.length; i++) {
// `Propuesta({...})` crea un temporal
// objeto Propuesto y `proposals.push(...)`
// lo anexa al final de las «propuestas». `.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
// Dar al votante el derecho a votar en esta votacion electoral.
// Sólo puede ser llamado por `chairperson`.
function giveRightToVote(address voter) {
// Si el argumento de `` require` evalúa a false`,,
// termina y revierte todos los cambios
// del estado y los Ether balances. Son a menudo
// una buena idea usar si las funciones
// se llaman incorrectamente. Pero ten cuidado, este
// consumirá actualmente todos los gases suministrados
// (esto está previsto que cambie en el futuro).
require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}
/// Delegar su voto al votante `to`.
function delegate(address to) {
// asigna la referencia
Voter storage sender = voters[msg.sender];
require(!sender.voted);
// La autodelegacion no está permitida.
require(to != msg.sender);
// Reenviar la delegación siempre y cuando
// `to` también se delegue.
// En general, estos bucles son muy peligrosos,
// porque si pasan demasiado tiempo, podrían
// necesitar más gas del que está disponible en un bloque.
// En este caso, la delegación no se ejecutará,
// pero en otras situaciones, tales bucles podrían
// hacer que un contrato se "atasque" completamente.
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
Encontramos un bucle en la delegación, no permitido.
require(to != msg.sender);
}
// Puesto que `sender` es una referencia,
// modifica el `voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter storage delegate = voters[to];
if (delegate.voted) {
// Si el delegado ya votó,
// añadir directamente con el número de votos
proposals[delegate.vote].voteCount += sender.weight;
} else {
// Si el delegado no votó todavía,
// agrega su weight.
delegate.weight += sender.weight;
}
}
/// Dé su voto (incluidos los votos delegados a usted)
/// a la propuesta `proposals[proposal].name`.
function vote(uint proposal) {
Voter storage sender = voters[msg.sender];
require(!sender.voted);
sender.voted = true;
sender.vote = proposal;
// Si `proposal` está fuera del rango de la matriz o array,
// esto lanzará automáticamente y revertirá
// todos los cambios.
proposals[proposal].voteCount += sender.weight;
}
/// @dev Calcula la propuesta ganadora teniendo
/// en cuenta todos los votos anteriores.
function winningProposal() constant
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;
}
}
}
// Llama a la función winningProposal () para obtener el índice
// del ganador contenido en la matriz de propuestas y luego
// devuelve el nombre del ganador
function winnerName() constant
returns (bytes32 winnerName)
{
winnerName = proposals[winningProposal()].name;
}
}
Posibles mejoras¶
Actualmente, se necesitan muchas transacciones para asignar los derechos de voto a todos los participantes. ¿Puedes pensar en una mejor manera?
Subasta a ciegas¶
En esta sección, mostraremos lo fácil que es crear un contrato de subasta completamente a ciegas en Ethereum. Empezaremos con una subasta abierta donde todos podrán ver las ofertas que se hacen y luego extender este contrato a una subasta ciega donde no es posible ver la oferta real hasta que finalice el período de oferta.
Subasta sencilla ¶
La idea general del siguiente contrato de subasta simple es que todo el mundo puede enviar sus ofertas durante un período de licitación. Las ofertas ya incluyen el envío de dinero / ether con el fin de vincular a los licitantes a su oferta. Si la oferta más alta se eleva, el mejor postor anterior recupera su dinero. Después del final del período de licitación, el contrato debe ser llamado manualmente para que el beneficiario reciba su dinero - los contratos no pueden activarse por sí mismos.
pragma solidity ^0.4.11;
contract SimpleAuction {
// Parámetros de la subasta. Los tiempos son
// absoluto unix timestamps (segundos desde 1970-01-01)
// o períodos de tiempo en segundos.
address public beneficiary;
uint public auctionStart;
uint public biddingTime;
// Estado actual de la subasta.
address public highestBidder;
uint public highestBid;
// Permitieron los retiros de la
mapping(address => uint) pendingReturns;
// Se establece en true al final, no permite ningún cambio
bool ended;
// Eventos que se dispararán en los cambios.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// Lo que sigue es un comentario llamado natspec,
// reconocible por las tres barras.
// Se mostrará cuando se solicite al usuario
// que confirme una transacción.
/// Crear una subasta simple con `_biddingTime`
/// segundos de tiempo de licitación en nombre de la
/// dirección beneficiaria` _beneficiary`.
function SimpleAuction(
uint _biddingTime,
address _beneficiary
) {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _biddingTime;
}
/// Oferta en la subasta con el valor enviado
/// junto con esta transacción.
/// El valor sólo se reembolsará si
/// la subasta no se gana.
function bid() payable {
// No hay argumentos necesarios,
// toda la información ya forma parte de
// la transacción. La palabra clave a pagar
// se requiere para que la función
// pueda recibir Éther.
// Revertir la llamada si el período de
// licitación ha terminado.
require(now <= (auctionStart + biddingTime));
// Si la oferta no es superior, envíe el
// dinero de vuelta.
require(msg.value > highestBid);
if (highestBidder != 0) {
// devolver el dinero por el simple uso
// highestBidder.send(highestBid) es un riesgo de seguridad
// ya que podría ejecutar un contrato que no es de confianza.
// Siempre es más seguro dejar que los beneficiarios
// retiren su dinero ellos mismos.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
}
/// Retirar una oferta que fue sobrepasada.
function withdraw() returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// Es importante poner esto a cero porque el destinatario
// puede volver a llamar a esta función como parte de la llamada de recepción
// antes de que `send` devuelva.
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
// No hay necesidad de llamar a tirar aquí, sólo restablecer la cantidad adeudada
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// Finalizar la subasta y enviar la oferta más alta
/// al beneficiario.
function auctionEnd() {
// Es una buena guía para estructurar funciones que interactúan
// con otros contratos (es decir, que llamar a funciones o enviar Ether)
// en tres fases:
// 1. Condiciones de chequeos
// 2. realizar acciones ( Potencialmente cambiando las condiciones)
// 3. interactuando con otros contratos
// Si estas fases se mezclan, el otro contrato podría llamar
// de nuevo al contrato actual y modificar el estado o causar
// efectos (pago de ether) a realizar múltiples veces.
// Si las funciones llamadas internamente incluyen interacción con
// contratos, también deben ser considerados como interacción con
// contratos externos.
// 1. Condiciones
require(now >= (auctionStart + biddingTime));
// la subasta no terminó todavía
require(!ended);
// esta función ya ha sido llamada
// 2. Efectos
ended = true;
AuctionEnded(highestBidder, highestBid);
// 3. Interaccion
beneficiary.transfer(highestBid);
}
}
Subasta a ciegas¶
La subasta abierta anterior se extiende a una subasta ciega en lo siguiente. La ventaja de una subasta ciega es que no hay presión de tiempo hacia el final del período de licitación. Crear una subasta ciega en una plataforma informática transparente puede sonar como una contradicción, pero la criptografía viene al rescate.
Durante el período de licitación , un licitador no envía realmente su oferta, pero si una versión hashed de ella. Puesto que actualmente se considera prácticamente imposible encontrar dos valores (suficientemente largos) cuyos valores de hash sean iguales, el licitador se compromete a la oferta por eso. Después del final del período de licitación, los licitadores tienen que revelar sus ofertas: Envían sus valores sin cifrar y el contrato comprueba que el valor hash es el mismo que el proporcionado durante el período de licitación.
Otro desafío es cómo hacer la subasta obligatoria y ciega al mismo tiempo: La única manera de evitar que el postor simplemente no envíe el dinero después de que ganó la subasta es hacerla enviarlo junto con la oferta. Dado que las transferencias de valores no pueden ser cegadas en Ethereum, cualquiera puede ver el valor.
El siguiente contrato resuelve este problema aceptando cualquier valor que sea mayor que la oferta más alta. Dado que esto sólo puede comprobarse durante la fase de revelación, algunas ofertas pueden ser inválidas, y esto es a propósito (incluso proporciona un indicador explícito para colocar ofertas no válidas con transferencias de alto valor): Los postores pueden confundir la competencia al bajar ofertas inválidas.
pragma solidity ^0.4.11;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address public beneficiary;
uint public auctionStart;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// Permitieron los retiros de la asignación de ofertas anteriores
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
/// Los modificadores son una manera conveniente de validar las entradas a las funciones
/// `onlyBefore` se aplica a` bid` a continuación:
/// El nuevo cuerpo de la función es el cuerpo del modificador donde
/// `_` es reemplazado por el cuerpo de la antigua función.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
function BlindAuction(
uint _biddingTime,
uint _revealTime,
address _beneficiary
) {
beneficiary = _beneficiary;
auctionStart = now;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
/// Coloca una puja ciega con `_blindedBid` = keccak256 (valor,
/// falso, secreto).
/// El ether enviado sólo se devuelve si la oferta es correcta
/// revelada en la fase reveladora. La oferta es válida si el
/// el ether enviado junto con la oferta es al menos "value" y
/// "fake" si no es cierto. Establecer "fake" a true y enviar
/// no la cantidad exacta son formas de ocultar la oferta real pero
/// sigue haciendo el depósito requerido. La misma dirección puede
/// realizar varias pujas.
function bid(bytes32 _blindedBid)
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
/// Revelar sus ofertas ciegas. Obtendrá un reembolso por todas las
/// pujas inválidas correctamente ocultas y para todas las pujas excepto para
/// la más alta.
function reveal(
uint[] _values,
bool[] _fake,
bytes32[] _secret
)
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
var bid = bids[msg.sender][i];
var (value, fake, secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(value, fake, secret)) {
// La oferta no fue realmente revelada.
// No devuelve el depósito.
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// Hacer imposible que el remitente reclame
// el mismo depósito.
bid.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
// Esta es una función "interna" que significa que
// sólo se puede llamar desde el propio contrato (o de
// contratos derivados).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != 0) {
// reembolsará el postor más alto previamente.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// Retirar una oferta que fue sobrepasada.
function withdraw() {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// Es importante establecer esto a cero porque el destinatario
// puede volver a llamar esta función como parte de la llamada de recepción
// antes de que `send` regrese (véase la observación anterior acerca de
// condiciones -> efectos -> interaccion).
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
/// Finalizar la subasta y enviar la oferta más alta
/// al beneficiario.
function auctionEnd()
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
// Enviamos todo el dinero que tenemos, porque algunos
// de los reembolsos pudieron haber fallado.
beneficiary.transfer(this.balance);
}
}
Compra remota segura¶
pragma solidity ^0.4.11;
contract Purchase {
uint public value;
address public seller;
address public buyer;
enum State { Created, Locked, Inactive }
State public state;
// Asegúrese de que `msg.value` sea un número par.
// La división trunca si es un número impar.
// Comprueba a través de la multiplicación que no era un número impar.
function Purchase() payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value);
}
modifier condition(bool _condition) {
require(_condition);
_;
}
modifier onlyBuyer() {
require(msg.sender == buyer);
_;
}
modifier onlySeller() {
require(msg.sender == seller);
_;
}
modifier inState(State _state) {
require(state == _state);
_;
}
event Aborted();
event PurchaseConfirmed();
event ItemReceived();
/// Abortar la compra y recuperar el ether.
/// Sólo puede ser llamado por el vendedor antes
/// el contrato está bloqueado.
function abort()
onlySeller
inState(State.Created)
{
Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}
/// Confirme la compra como comprador.
/// La transacción tiene que incluir el ether `2 * value`.
/// El éter se bloquea hasta que se llama a
/// ConfirmReeceived.
function confirmPurchase()
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
/// Confirme que usted (el comprador) recibió el artículo.
/// Esto liberará el ether bloqueado.
function confirmReceived()
onlyBuyer
inState(State.Locked)
{
ItemReceived();
// Es importante cambiar primero el estado porque
// de lo contrario, los contratos llamados usando `send` debajo
// pueden volver a llamar aquí.
state = State.Inactive;
// NOTA: Esto realmente permite que tanto el comprador como el vendedor
// bloqueen el reembolso - el patrón de retirada debe ser usado.
buyer.transfer(value);
seller.transfer(this.balance);
}
}
Canal de micropago¶
Por ser escrito.
Solidity en profundidad¶
Esta sección debe proporcionarle todo lo que necesita saber sobre Solidity. Si algo falta aquí, póngase en contacto con nosotros en Gitter o haga una solicitud de extracción(pull request) en Github.
Diseño de un archivo de origen de Solidity¶
os archivos de origen pueden contener un número arbitrario de definiciones de contratos, incluir directivas y directivas de pragma.
Version Pragma¶
Los archivos fuente pueden (y deben) ser anotados con una denominada version pragma para rechazar
la compilación con futuras versiones del compilador que podrían introducir cambios incompatibles.
Tratamos de mantener tales cambios a un mínimo absoluto y especialmente introducir cambios de una
manera que los cambios en la semántica también requieren cambios en la sintaxis, pero esto no siempre es posible.
Debido a eso, es siempre una buena idea leer a través del changelog por lo menos para los lanzamientos con cambios importantes,
esos lanzamientos siempre tendrán versiones del formato
0.x.0
o x.0.0
.
El pragma de la versión se utiliza de la siguiente manera:
pragma solidity ^0.4.0;
Dicho archivo de origen no se compilará con un compilador anterior a la versión 0.4.0
y tampoco funcionará en un compilador a partir de la versión 0.5.0 (esta segunda condición se agrega mediante el uso
^
). La idea detrás de esto es que no habrá cambios de ruptura hasta la versión
0.5.0
0.5.0, por lo que siempre podemos estar seguros
de que nuestro código compilará la forma en que lo intentamos. No arreglamos la versión exacta del compilador,
de modo que las versiones de bugfix todavía son posibles.
Es posible especificar reglas mucho más complejas para la versión del compilador, la expresión sigue las utilizadas por npm.
Importar otros archivos de origen¶
Sintaxis y Semántica¶
Solidity soporta declaraciones de importación que son muy similares a las disponibles en JavaScript (desde ES6 en), aunque Solidity no conoce el concepto de “exportación predeterminada”.
A nivel global, puede utilizar declaraciones de importación del siguiente formulario:
import "filename";
Esta instrucción importa todos los símbolos globales de “filename” (y símbolos importados allí) al ámbito global actual (diferente de ES6 pero compatible con Solidity).
import * as symbolName from "filename";
... crea un nuevo símbolo global symbolName
cuyos miembros son todos los símbolos globales "filename ó nombre del archivo"
.
import {symbol1 as alias, symbol2} from "filename";
... crea nuevos símbolos globales alias
y symbol2
que hacen referencia a symbol1
y symbol2
desde "filename"
, respectivamente.
Otra sintaxis que no es parte de ES6, pero probablemente conveniente:
import "filename" as symbolName;
que es equivalente a import * as symbolName from "filename";
.
Paths o Directorios o Rutas o Trayectorias¶
En lo anterior, filename
siempre se trata como una ruta
/
como directorio separador,
.
como el actual y ..
como el directorio padre. Cuando .
o
..
es seguido por un carácter excepto
/
,
no se considera como el actual o el directorio padre. Todos los nombres de rutas se tratan como rutas absolutas
a menos que comiencen con el directorio actual .
o el directorio padre
..
.
Para importar un archivo x
desde el mismo directorio que el archivo actual, utilice
import "./x" as x;
.
Si se utiliza import "x" as
x;
en su lugar, se podría referenciar un archivo diferente
(en un directorio global de inclusion “include directory”).
Depende del compilador (ver abajo) cómo resolver realmente las trayectorias. En general, la jerarquía de directorios no necesita mapear estrictamente su sistema de archivos local, sino que también puede asignar recursos descubiertos a través, por ejemplo, de ipfs, http o git.
Uso en compiladores reales¶
Cuando se invoca el compilador, no sólo es posible especificar cómo descubrir el primer elemento de una ruta,
sino que es posible especificar remappings de prefijo de ruta de modo que e.g.
github.com/ethereum/dapp-bin/library
es remapeado a
/usr/local/dapp-bin/library
el compilador leerá los archivos desde allí.
Si se pueden aplicar varios remappings, se intentará primero el que tenga la clave más larga.
Esto permite un “fallback-remapping” con, por ejemplo, ""
mapas a
"/usr/local/include/solidity"
. Además, estos reasignamientos pueden
depender del contexto, que le permite configurar paquetes para importar, por ejemplo, diferentes versiones de una biblioteca del mismo nombre.
solc:
Para solc (el compilador de línea de comandos), estos reasignaciones se proporcionan como
context:prefix=target
argumentos, donde tanto el
context:
y las
=target
partes son opcionales (donde predeterminado de destino prefijo en ese caso). Todos los valores de reasignación que
son archivos regulares se compilan (incluyendo sus dependencias). Este mecanismo es totalmente compatible con
versiones anteriores (siempre y cuando ningún nombre de archivo contenga = o :) y por lo tanto no haya un cambio de ruptura.
Todas las importaciones en archivos en o debajo del directorio context
que importan un archivo que comienza con prefix
se redirigen reemplazando prefix
por target
.
Por ejemplo, si clona
github.com/ethereum/dapp-bin/
localmente a
/usr/local/dapp-bin
, puede utilizar lo siguiente en su archivo de origen:
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
y ejecute el compilador como
solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol
Como un ejemplo más complejo, suponga que confía en algún módulo que utiliza una versión muy antigua de dapp-bin.
Esa versión antigua de dapp-bin está desprotegida /usr/local/dapp-bin_old
, entonces usted puede usar
solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
source.sol
de modo que todas las importaciones en el module2
tienen la versión antigua, pero las importaciones en module1
obtienen la nueva versión.
Tenga en cuenta que solc sólo permite incluir archivos de determinados directorios:
Tienen que estar en el directorio (o subdirectorio) de uno de los archivos de origen
explícitamente especificados o en el directorio (o subdirectorio) de un destino de reasignación.
Si desea permitir absoluto absoluto incluye, sólo tiene que agregar la reasignación(remapping) =/
.
Si hay varios remappings que conducen a un archivo válido, se selecciona la reasignación con el prefijo común más largo.
Remix:
Remix
Remix proporciona una reasignación automática para github y también recuperará automáticamente el archivo
a través de la red: Puede importar la correlación iterable por ejemplo
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
.
Otros proveedores de código fuente se pueden agregar en el futuro.
Comentarios¶
Los comentarios de una línea (//
) y los comentarios de varias líneas
(/*...*/
) son posibles.
// Este es un comentario de una sola línea.
/*
Este es un
comentario de varias líneas.
*/
Además, hay otro tipo de comentario llamado comentario natspec, para el que la documentación aún no está escrita.
Se escriben con una barra triple (///
)
o un bloque de doble asterisco(/** ... */
) y
deben utilizarse directamente sobre declaraciones de funciones o sentencias.
Puede utilizar etiquetas de estilo Doxygen
-dentro de estos comentarios para documentar funciones,
anotar condiciones para la verificación formal y proporcionar
un texto de confirmación que se muestra a los usuarios cuando
intentan invocar una función.
En el siguiente ejemplo documentamos el título del contrato, la explicación de los dos parámetros de entrada y dos valores devueltos.
pragma solidity ^0.4.0;
/** @title Shape Calculadora. */
contract shapeCalculator {
/** @dev Calcula la superficie y el perímetro de un rectángulo.
* @param w Ancho del rectángulo.
* @param h Altura del rectángulo.
* @return s La superficie calculada.
* @return p El perímetro calculado.
*/
function rectangle(uint w, uint h) returns (uint s, uint p) {
s = w * h;
p = 2 * (w + h);
}
}
Estructura de un Contrato¶
Los Contratos en Solidity son similares a las clases en lenguajes orientados a objetos. Cada contrato puede contener declaraciones de Variables de Estado, Funciones, Modificadores de Funciones, Eventos , Tipos de Structs y Tipos de Enum. Además, los contratos pueden heredar de otros contratos.
Variables de estado¶
Las variables de estado son valores que permanentemente se almacenan en el storage de un contrato.
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData; // Variable de estado
// ...
}
Consulte la sección Types para tipos de variables de estado válidos y Visibilidad y Getters para las posibles opciones de visibilidad.
Funcciones¶
Las funciones son las unidades de código ejecutables dentro de un contrato.
pragma solidity ^0.4.0;
contract SimpleAuction {
function bid() payable { // Funcion
// ...
}
}
Las llamadas a Funciones pueden ocurrir internamente o externamente y tienen diferentes niveles de visibilidad ( Visibilidad y Getters ) hacia otros contratos.
Modificadores de funciones¶
Los modificadores de función se pueden utilizar para enmendar la semántica de las funciones de forma declarativa (véase la seccion Modificadores de funciones en los contratos).
pragma solidity ^0.4.11;
contract Purchase {
address public seller;
modifier onlySeller() { // Modificador
require(msg.sender == seller);
_;
}
function abort() onlySeller { // Modificador de uso
// ...
}
}
Eventos¶
Los eventos son interfaces de conveniencia con las instalaciones de registro de EVM.
pragma solidity ^0.4.0;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Evento
function bid() payable {
// ...
HighestBidIncreased(msg.sender, msg.value); // Evento desencadenante
}
}
Consulte la sección Eventos en contratos para obtener información sobre cómo se declaran los eventos y se pueden utilizar desde dapp.
Types¶
es un lenguaje escrito de forma estática, lo que significa que el tipo de cada variable (estatal y local) necesita ser especificado (o al menos conocido - vea Deducción Type a continuación) en tiempo de compilación. Solidity proporciona varios tipos elementales que se pueden combinar para formar tipos complejos.
Además, los tipos pueden interactuar entre sí en expresiones que contienen operadores. Para una referencia rápida de los distintos operadores, véase Orden de Precedencia de Operadores.
Tipos de valor¶
Los tipos siguientes también se denominan tipos de valores porque las variables de estos tipos siempre se pasan por valor, es decir, siempre se copian cuando se utilizan como argumentos de función o en asignaciones.
Booleanos¶
bool
: Los valores posibles son constantes
true
y false
.
Operadores:
!
(Negación lógica)&&
(Conjunción lógica, “and”)||
(Disyunción lógica, “or”)==
(igualdad)!=
(desigualdad)
Los operadores ||
y &&
aplican las normas comunes de cortocircuito. Esto significa que en la expresión
f(x) || g(y)
,
if f(x)
se evalúa true
,
g(y)
no se evaluará aunque tenga efectos secundarios.
Enteros¶
int
/ uint
:
Enteros firmados y sin signo de varios tamaños. Las palabras clave uint8
to uint256
en pasos de 8
(sin signo de 8 hasta 256 bits) y int8
to
int256
. uint
and int
son alias para uint256
e int256
,
respectivamente.
Operadores:
- Comparaciones:
<=
,<
,==
,!=
,>=
,>
(son de tipobooleanos
) - Operadores de bits:
&
,|
,^
(bit a bit exclusivo o),~
(bit a bit negación) - Los operadores aritméticos:
+
,-
, unary-
, unary+
,*
,/
,%
(el residuo de la division),**
(exponentiacion),<<
(left shift o desplazamiento a la izquierda),>>
(right shift o desplazamiento a la derecha)
La división siempre trunca (sólo se compila con código de operación del EVM DIV
),
pero no trunca si ambos operadores son literals
(o expresiones literales).
División por cero y módulo con cero lanza una excepción de tiempo de ejecución.
El resultado de una operación de cambio es el tipo del operando izquierdo. La expresión
x << y
es equivalente a
x * 2**y
, y
x >> y
es equivalente a x / 2**y
.
Esto significa que se extiende el signo numérico negativo. El cambio de una cantidad negativa arroja una excepción de tiempo de ejecución.
Advertencia
Los resultados producidos por el desplazamiento a la derecha de los valores negativos de tipos enteros con signo son diferentes de los producidos por otros lenguajes de programación. En Solidity, desplace los mapas de la derecha hasta la división para que los valores negativos desplazados se redondeen hacia cero (truncado). En otros lenguajes de programación, el desplazamiento a la derecha de valores negativos funciona como la división con el redondeo hacia abajo (hacia el infinito negativo).
Direcciónes¶
address
: Contiene un valor de 20 bytes (tamaño de una dirección Ethereum).
Los tipos de dirección también tienen miembros y sirven como base para todos los contratos.
Operadores:
<=
,<
,==
,!=
,>=
and>
Members of Addresses o Miembros de Direcciones¶
balance
ytransfer
Para obtener una referencia rápida, consulte Address Related.
Es posible consultar el balance de una dirección utilizando la propiedad
balance
y enviar Ether (en unidades de wei) a una dirección usando la funcion transfer
:
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
Nota
Si x
es una dirección de contrato, su código
(más específicamente: su función de fallback, si está presente) se ejecutará junto con la llamada
transfer
(esto es una limitación del EVM y no se puede evitar).
Si esa ejecución se queda sin gas o falla de alguna manera,
la transferencia de ether se revertirá y el contrato actual se detendrá con una excepción.
send
Send es la contraparte de bajo nivel de transfer
.
Si la ejecución falla, el contrato actual no se detendrá con una excepción, pero send
retornará false
.
Advertencia
Hay algunos peligros en el uso de send
:
La transferencia falla si la profundidad de la pila de llamadas está en 1024
(esto siempre puede ser forzado por la persona que llama) y también falla si el
receptor se queda sin gas. Así que para hacer transferencias de éter seguro,
siempre verifique el valor de retorno de
send
, utilice transfer
o incluso mejor: use un patrón donde el destinatario retira el dinero.
call
,callcode
ydelegatecall
Además, para interactuar con contratos que no se adhieren a la ABI,
se proporciona la función call
que toma un número arbitrario de argumentos de cualquier tipo.
Estos argumentos se rellenan a 32 bytes y se concatenan. Una excepción es el caso en el que
el primer argumento está codificado en exactamente cuatro bytes.
En este caso, no se rellena para permitir el uso de firmas de función aquí.
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
Devuelve un booleano que indica si la función invocada terminó
(true
) o si se provocó una excepción EVM
(false
). No es posible acceder a los datos reales devueltos (para esto necesitaríamos conocer la codificación y el tamaño por adelantado).
De forma similar, la funcion delegatecall
se puede utilizar: la diferencia es que sólo se utiliza el código de la dirección dada, todos los demás aspectos (almacenamiento, equilibrio, ...)
se toman del contrato actual. El propósito de
delegatecall
es utilizar código de la libreria que se almacena en otro contrato.
El usuario tiene que asegurarse de que el diseño de almacenamiento en ambos contratos
sea adecuado para que la delegatecall sea usada.
Antes de homestead, sólo estaba disponible una variante limitada llamada
callcode
que no proporcionaba acceso al valor original
msg.sender
y msg.value
.
Las tres funciones call
, delegatecall
y callcode
son funciones de muy bajo nivel y sólo deben utilizarse como un
último recurso ya que rompen el tipo de seguridad de Solidity.
La opcion.gas()
está disponible en los tres métodos, mientras que la opción
.value()
no es compatible con
delegatecall
.
Nota
Todos los contratos heredan los miembros de la dirección, por lo que es posible consultar el saldo del contrato actual utilizando
this.balance
.
Nota
El uso de callcode
es desalentado y se eliminará en el futuro.
Advertencia
Todas estas funciones son funciones de bajo nivel y deben utilizarse con cuidado. Específicamente, cualquier contrato desconocido podría ser malicioso y si lo llamas, entregas el control a ese contrato que a su vez podría volver a llamar a tu contrato, así que prepárate para cambios en tus variables de estado cuando vuelva la llamada.
Arrays de bytes de tamaño fijo¶
bytes1
, bytes2
, bytes3
, ..., bytes32
. byte
es un alias para bytes1
.
Operadores:
- Comparaciones:
<=
,<
,==
,!=
,>=
,>
(son de tipobool
) - Operadores de bits:
&
,|
,^
(bit a bit o exclusiva),~
(negación bit a bit),<<
(left shift o desplazamiento a la izquierda),>>
(right shift o desplazamiento a la derecha) - Índice de Acceso: Si
x
es de tipobytesI
, a continuaciónx[k]
para0 <= k < I
devuelve el bytek
º (sólo lectura).
The shifting operator works with any integer type as right operand (but will return the type of the left operand), which denotes the number of bits to shift by. Shifting by a negative amount will cause a runtime exception. El operador de desplazamiento trabaja con cualquier tipo de número entero como operando a la derecha (pero devolverá el tipo del operando izquierdo), que indica el número de bits por los que cambiar. El cambio de una cantidad negativa causará una excepción de tiempo de ejecución.
Miembros:
.length
Produce la longitud fija de la matriz de bytes (sólo lectura).
Matriz o Array de bytes de tamaño dinámico¶
bytes
:- Matriz de bytes de tamaño dinámico, vea Arrays. No es un tipo de valor!
string
:- Cadena codificada UTF-8 de tamaño dinámico, vea Arrays. No es un tipo de valor!
Como regla general, utilice bytes
datos de bytes crudos de longitud arbitraria y datos
string
de cadena de longitud arbitraria (UTF-8). Si puede limitar la longitud a un cierto número de bytes, utilice siempre uno de
bytes1
a bytes32
porque son mucho más baratos.
Fixed Point Numbers o Números de puntos fijos¶
Advertencia
Los números de puntos fijos aún no están completamente soportados por Solidity. Pueden ser declarados, pero no se pueden asignar.
Address Literals o Direcciónes Literales¶
Los literales hexadecimales que pasan la prueba de comprobación de dirección, por ejemplo,
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
son de tipo
address
.
Los literales hexadecimales que tienen entre 39 y 41 dígitos de largo y no pasan la prueba de suma de comprobación producen una advertencia
y son tratados como literales de números racionales regulares.
Racionales y Enteros Literales¶
Los literales enteros se forman a partir de una secuencia de números en el rango de 0-9. Se interpretan como decimales. Por ejemplo,
69
significa sesenta y nueve.
Los literales octales no existen en Solidity y los ceros iniciales son inválidos.
Los literales de fracción decimal están formados por un .
con al menos un número en un lado. Los ejemplos incluyen
1.
, .1
y
1.3
.
También se admite la notación científica, donde la base puede tener fracciones, mientras que el exponente no puede. Los ejemplos incluyen
2e10
, -2e10
, 2e-10
, 2.5e1
.
Las expresiones literales de número conservan precisión arbitraria hasta que se convierten en un tipo no literal (es decir, al utilizarlas junto con una expresión no literal). Esto significa que los cálculos no se desbordan y las divisiones no se truncan en el número de expresiones literales.
Por ejemplo, (2**800 + 1) -
2**800
los resultados en la constante 1
(de tipo uint8
)
aunque los resultados intermedios ni siquiera se ajustan al lenguaje de la máquina. Además, .5
* 8
resulta en el entero
4
(aunque los no enteros se utilizaron en el medio).
Cualquier operador que se puede aplicar a números enteros también se puede aplicar a expresiones literales de números siempre y cuando los operandos sean enteros. Si cualquiera de los dos es fraccional, las operaciones de bits no son permitidas y la exponenciación se rechaza si el exponente es fraccional (porque podría resultar en un número no racional).
Nota
Solidity tiene un número de tipo literal para cada número racional. Los literales enteros y
los literales de números racionales pertenecen a tipos literales de número. Además,
todas las expresiones literales numéricas (es decir, las expresiones que contienen
sólo literales y operadores de números) pertenecen a tipos literales de número.
Así que el número de expresiones literales 1
+ 2
y 2
+ 1
ambos pertenecen al mismo número de tipo literal para el número racional tres.
Advertencia
División de literales enteros se utilizaron como truco en versiones anteriores, pero ahora se convertirá en un número racional, es decir,
5 / 2
no es igual a
2
, pero si a 2.5
.
Nota
Las expresiones literales numéricas se convierten en un tipo no literal tan pronto como se utilizan con expresiones no literales.
Aunque sabemos que el valor de la expresión asignada a b
en el siguiente ejemplo se evalúa como un entero, pero la expresión parcial
2.5 + a
no es aprobada entonces el código no compila.
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
String Literals¶
Los literales de cadena se escriben con comillas dobles o simples
("foo"
o 'bar'
).
No implican ceros a la izquierda como en C; "foo"
representa tres bytes no cuatro.
Al igual que con literales enteros, su tipo puede variar, pero son implícitamente convertibles a bytes1
,
..., bytes32
, si encajan, a bytes
y para string a string
.
Los literales de cadenas admiten caracteres de escape, como \n
,
\xNN
y \uNNNN
.
\xNN
toma un valor hexadecimal e inserta el byte apropiado, mientras
\uNNNN
toma un codigo Unicode e inserta una secuencia UTF-8.
Hexadecimal Literals¶
Los literales hexadecimales están prefijados con la palabra clave hex
y están encerrados entre comillas dobles o simples (hex"001122FF"
).
Su contenido debe ser una cadena hexadecimal y su valor será la representación binaria de esos valores.
Los literales de Hexademical se comportan como literales de la secuencia y tienen las mismas restricciones de la convertibilidad.
Enums¶
Enums son una forma de crear un tipo definido por el usuario en Solidity. Están explícitamente convertibles a / y desde todos los tipos de entero, pero no se permite la conversión implícita. Las conversiones explícitas comprueban los rangos de valores en tiempo de ejecución y un error produce una excepción. Enums necesita al menos un miembro.
pragma solidity ^0.4.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() {
choice = ActionChoices.GoStraight;
}
// Puesto que los tipos enum no forman parte de la ABI, la firma "getChoice"
// se cambiará automáticamente a "getChoice() returns (uint8)"
// para todos los asuntos externos a Solidity. El tipo de entero utilizado es simplemente
// lo suficientemente grande como para contener todos los valores de enum, es decir, si tiene más valores,
// `uint16` se utilizará y así sucesivamente.
function getChoice() returns (ActionChoices) {
return choice;
}
function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}
Tipos de funciones¶
Son los tipos de funciones. Las variables de tipo de función se pueden asignar desde funciones y los parámetros de función de tipo de función se pueden utilizar para pasar funciones y devolver funciones de llamadas de función. Los tipos de funciones vienen en dos variantes : - internal y external :
Las funciones internas sólo pueden ser llamadas dentro del contrato actual (más específicamente, dentro de la unidad de código actual, que también incluye funciones de biblioteca interna y funciones heredadas) porque no se pueden ejecutar fuera del contexto del contrato actual. Llamar a una función interna se realiza al saltar a su etiqueta de entrada, al igual que al llamar una función del contrato actual internamente.
Las funciones externas consisten en una dirección y una firma de función y pueden ser pasadas y devueltas por las llamadas de función externas.
Los tipos de funciones se registran de la siguiente manera:
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
En contraste con los tipos de parámetros, los tipos de retorno no pueden estar vacíos - si el tipo de
función no devuelve nada, se debe omitir toda la parte returns
(<return types>)
.
Por defecto, los tipos de función son internos, por lo que la palabra clave
internal
se puede omitir.
Por el contrario, las funciones de contrato son públicas por defecto,
sólo cuando se utilizan como el nombre de un tipo, el valor predeterminado es interno.
Hay dos maneras de acceder a una función en el contrato actual: Ya sea directamente por su nombre
f
,
o mediante this.f
.
La primera resultará en una función interna, la última en una función externa.
Si no se inicializa una variable de tipo de función, llamarla dará lugar a una excepción.
Lo mismo sucede si llama a una función después de usar delete
.
Si se utilizan tipos de funciones externas fuera del contexto de Solidity, se tratan como de tipo
function
que codifica la dirección seguida por el identificador de función en un solo tipo
bytes24
.
Tenga en cuenta que las funciones públicas del contrato actual pueden utilizarse tanto como una función interna como como una función externa.
Para usar f
como una función interna,
sólo usa f
,
si quieres usar su forma externa, usa this.f
.
Ejemplo que muestra cómo usar los tipos de funciones internas:
pragma solidity ^0.4.5;
library ArrayUtils {
// ilas funciones internas se pueden usar en las funciones de biblioteca interna porque
// serán parte del mismo contexto de código
function map(uint[] memory self, function (uint) returns (uint) f)
internal
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) returns (uint) f
)
internal
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal returns (uint) {
return x + y;
}
}
Otro ejemplo que utiliza tipos de funciones externas:
pragma solidity ^0.4.11;
contract Oracle {
struct Request {
bytes data;
function(bytes memory) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) {
// Aquí se comprueba que la respuesta proviene de una fuente de confianza
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // contrato reconocido
function buySomething() {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) {
require(msg.sender == address(oracle));
// Usar los datos
}
}
Nota
Lambda o funciones en línea están planificadas, pero aún no están soportadas.
Tipos de referencia¶
Los tipos complejos, es decir, los tipos que no siempre encajan en 256 bits tienen que ser manejados con más cuidado que los tipos de valores que ya hemos visto. Dado que copiarlos puede ser bastante costoso, tenemos que pensar si queremos que se almacenen en memory (que no es persistente) o en storage (donde se mantienen las variables de estado).
Localización de datos¶
Cada tipo complejo, es decir, arrays y structs, tienen una anotación adicional, la
“data location”, acerca de si se almacena en la memoria o en almacenamiento.
Dependiendo del contexto, siempre hay un valor predeterminado,
pero se puede anular añadiendo storage
o
memory
al tipo.
El valor por defecto para los parámetros de la función (incluidos los parámetros de retorno) es
memory
,
el valor por defecto para las variables locales es storage
y la ubicación es forzada a
storage
para las variables de estado (obviamente).
También hay una tercera ubicación de datos, calldata
,
que es un área no modificable y no persistente donde se almacenan argumentos de función. Los parámetros de función
(no los parámetros de retorno) de las funciones externas son forzados a calldata
y se comportan principalmente como memory
.
Las ubicaciones de datos son importantes porque cambian la forma en que se comportan las asignaciones: las asignaciones entre almacenamiento y memoria y también a una variable de estado (incluso de otras variables de estado) siempre crean una copia independiente. Sin embargo, las asignaciones a variables de almacenamiento locales sólo asignan una referencia, y esta referencia siempre apunta a la variable de estado incluso si ésta se cambia mientras tanto. Por otro lado, las asignaciones de un tipo de referencia almacenado en memoria a otro tipo de referencia almacenado en memoria no crean una copia.
pragma solidity ^0.4.0;
contract C {
uint[] x; // la ubicación de datos de x es storage
// la ubicación de los datos es memoryArray
function f(uint[] memoryArray) {
x = memoryArray; // funciona, copia toda la matriz a storage
var y = x; // trabaja, asigna un puntero, la localización de datos de "y" es storage
y[7]; // fino, devuelve el octavo elemento
y.length = 2; // fino, modifica x a través de y
delete x; // fino, borra la matriz, también modifica "y"
// Lo siguiente no funciona; necesitaría crear una nueva matriz temporal /
// sin nombre en storage, pero el almacenamiento está "estáticamente" asignado:
// y = memoryArray;
// Esto tampoco funciona, ya que restablecerá "reset" el puntero, pero no
// hay ninguna ubicación razonable a la que pueda apuntar.
// delete y;
g(x); // llama g, entregando una referencia a x
h(x); // llama h y crea una copia independiente y temporal en la memoria
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) {}
}
Resumen¶
- Localización de datos forzados:
- parámetros (no return) de funciones externas: calldata
- variables de estado: storage
- Ubicación de los datos predeterminados:
- parámetros (también los return) de las funciones: memory
- todas las demás variables locales: storage
Arrays o Matrices¶
Las matrices pueden tener un tamaño fijo en tiempo de compilación o pueden ser dinámicas. Para los arrays de storage, el tipo de elemento puede ser arbitrario (es decir, también otros arrays, mappings o structs). Para matrices de memory, no puede ser una asignación "mapping" y tiene que ser un tipo ABI si es un argumento de una función públicamente visible.
Una matriz de tamaño fijo k
y tipo de elemento T
se escribe como T[k]
,
una matriz de tamaño dinámico como T[]
. Como un ejemplo, una matriz dinámica de 5 posiciones de
uint
es uint[][5]
(tenga en cuenta que la notación se invierte en comparación con algunos otros lenguajes).
Para acceder a la segunda uint en la tercera posicion de la matriz dinámica, se utiliza
x[2][1]
(los índices son basados en cero y el acceso funciona de manera opuesta a la declaración, es decir,
x[2]
se corta un nivel en el tipo de la derecha).
Las Variables de tipo bytes
y string
son matrices especiales. Un bytes
es similar a byte[]
,
pero se empaqueta firmemente en calldata. string
es igual a bytes
pero no permite acceso de longitud "length" o índice "index" (por ahora).
Así que siempre se debe preferir bytes
over byte[]
porque es más barato.
Nota
Si desea acceder a la representación de bytes de un string s
, use
bytes(s).length
/ bytes(s)[7]
= 'x';
.
Tenga en cuenta que está accediendo a los bytes de bajo nivel de la representación UTF-8,
y no a los caracteres individuales!
Es posible marcar arrays como public
y permitir a Solidity crear un
getter.
El índice numérico se convertirá en un parámetro necesario para el getter.
Asignación de matrices de memoria (Allocating Memory Arrays)¶
Se pueden crear de Arrays con longitud variable en memory utilizando la palabra clave new
.
A diferencia de los Arrays en storage, que no es posible cambiar el tamaño de memory arrays mediante la asignación al miembro
.length
.
pragma solidity ^0.4.0;
contract C {
function f(uint len) {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
// Aquí tenemos a .length == 7 y b.length == len
a[6] = 8;
}
}
Array Literales / Arrays en Línea¶
Los arrays literales son arreglos que se escriben como una expresión y no se asignan a una variable de inmediato.
pragma solidity ^0.4.0;
contract C {
function f() {
g([uint(1), 2, 3]);
}
function g(uint[3] _data) {
// ...
}
}
El tipo de una matriz literal es un memory array de tamaño fijo cuyo tipo de base es el tipo común de los elementos dados. El tipo de
[1, 2, 3]
es
uint8[3] memory
,
porque el tipo de cada una de estas constantes es uint8
.
Debido a eso, fue necesario convertir el primer elemento en el ejemplo anterior a
uint
.
Tenga en cuenta que en la actualidad, las matrices de memoria de tamaño fijo no se pueden asignar
a matrices de memoria de tamaño dinámico, es decir, no es posible lo siguiente:
// Esto no compilará.
pragma solidity ^0.4.0;
contract C {
function f() {
// La siguiente línea crea un error de tipo porque uint [3] memory
// no se puede convertir en uint [] memory.
uint[] x = [uint(1), 3, 4];
}
}
Se planea eliminar esta restricción en el futuro, pero actualmente crea algunas complicaciones debido a cómo los arrays se pasan en el ABI.
Members o Miembros¶
- longitud "length":
- Las matrices tienen un miembro
length
para mantener su número de elementos. Los arrays dinámicos se pueden cambiar el tamaño en el almacenamiento "storage" (no en la memoria "memory") cambiando el miembro.length
. Esto no ocurre automáticamente al intentar acceder a elementos fuera de la longitud actual. El tamaño de las matrices de memoria es fijo (pero dinámico, es decir, puede depender de los parámetros de tiempo de ejecución) una vez que se crean. - push:
- Arrays de almacenamiento dinámico y
bytes
(nostring
) tienen un miembro o función de llamadapush
que se puede utilizar para añadir un elemento al final de la matriz. La función devuelve la nueva longitud.
Todavía no es posible utilizar matrices de matrices en funciones externas.
Advertencia
Debido a las limitaciones del EVM, no es posible devolver
el contenido dinámico de las llamadas de función externas. La función f
en
contract C { function
f() returns (uint[]) { ...
} }
devolverá algo si se llama desde web3.js, pero no si se llama desde Solidity.
La única solución para ahora es utilizar grandes matrices de tamaño estático.
pragma solidity ^0.4.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// Observe que lo siguiente no es un par de matrices dinámicas sino una
// matriz dinámica de pares (es decir, matrices de tamaño fijo de longitud dos).
bool[2][] m_pairsOfFlags;
// newPairs se almacena en la memoria - el valor predeterminado para los argumentos de la función
function setAllFlagPairs(bool[2][] newPairs) {
// la asignación a una matriz de almacenamiento reemplaza la matriz completa
m_pairsOfFlags = newPairs;
}
function setFlagPair(uint index, bool flagA, bool flagB) {
// el acceso a un índice no existente lanzará una excepción
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) {
// si el nuevo tamaño es menor, los elementos de matriz eliminados se borrarán
m_pairsOfFlags.length = newSize;
}
function clear() {
// estos borran los arrays completamente
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// efecto idéntico aquí
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes data) {
// matrices de bytes ( "bytes") son diferentes, ya que se almacenan sin relleno,
// pero se pueden tratar idéntica a "uint8 []"
m_byteData = data;
m_byteData.length += 7;
m_byteData[3] = 8;
delete m_byteData[2];
}
function addFlag(bool[2] flag) returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) returns (bytes) {
// Los arrays de memoria dinámica se crean usando `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Crear una matriz de bytes dinámicos:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(i);
return b;
}
span class="p">}
Structs¶
Solidity proporciona una forma de definir nuevos tipos en forma de estructuras, que se muestra en el siguiente ejemplo:
pragma solidity ^0.4.11;
contract CrowdFunding {
// Define un nuevo tipo con dos campos.
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
campaignID = numCampaigns++; // campaign ID es la variable de retorno
// Crea nueva estructura y guarda en almacenamiento. Dejamos de lado el tipo de mapeo..
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) payable {
Campaign storage c = campaigns[campaignID];
// Crea una nueva estructura de memoria temporal, inicializada con los valores dados
// y la copia en el almacenamiento.
// Observe que también puede usar Funder (msg.sender, msg.value) para inicializar.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
El contrato no ofrece la funcionalidad completa de un contrato de crowdfunding, pero contiene los conceptos básicos necesarios para entender las estructuras. Los tipos de estructura se pueden utilizar dentro de mappings y arrays y ellos mismos pueden contener mappings y arrays.
No es posible que una estructura contenga un miembro de su propio tipo, aunque la estructura misma puede ser el tipo de valor de un miembro de asignación(mapping). Esta restricción es necesaria, ya que el tamaño de la estructura tiene que ser finito.
Observe cómo en todas las funciones, un tipo de estructura se asigna a una variable local (de la ubicación de datos de almacenamiento predeterminada). Esto no copia la estructura pero sólo almacena una referencia para que las asignaciones a los miembros de la variable local realmente poder escribir en el estado.
Por supuesto, también puede acceder directamente a los miembros de la estructura sin asignarla a una variable local, como en
campaigns[campaignID].amount = 0
.
Mappings o Asignaciones ¶
Mapping types o Los tipos de asignación se declaran como mapping(_KeyType => _ValueType)
.
Aquí _KeyType
puede haber casi cualquier tipo excepto un mapeo, una matriz de tamaño dinámico, un contrato, un enum y una estructura.
_ValueType
puede ser de cualquier tipo, incluyendo asignaciones (mappings).
Las asignaciones se pueden ver como hash tables
que están virtualmente inicializadas de tal manera que cada clave
posible existe y se asigna a un valor cuya representación de bytes es todos ceros: el
default value de un tipo.
La similitud termina aquí, sin embargo: Los datos de la llave no se almacenan realmente en una correspondencia "mapping", solamente su
keccak256
hash usado para mirar para arriba el valor.
Debido a esto, las asignaciones no tienen una longitud o un concepto de una clave o valor que se "establece". “set”.
Las asignaciones sólo se permiten para variables de estado (o como tipos de referencia de almacenamiento en funciones internas).
Es posible declarar asignaciones public
y permitir a Solidity crear un getter.
El _KeyType
se convertirá en un parámetro necesario para el getter y retornará
_ValueType
.
El _ValueType
puede ser un mapping tambien. El getter tendrá un parámetro para cada uno
_KeyType
, recursivamente.
pragma solidity ^0.4.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(this);
}
}
Nota
Las asignaciones no son iterables, pero es posible implementar una estructura de datos encima de ellas. Para ver un ejemplo, consulte iterable mapping.
Operadores con LValues¶
Si a
es un LValue (es decir, una variable o algo que se puede asignar a), los siguientes operadores están disponibles como abreviaturas:
a += e
Es equivalente a
a = a +
e
. Los operadores -=
,
*=
, /=
,
%=
, a |=
,
&=
y ^=
se definen en consecuencia.
a++
y a--
son equivalentes a
a += 1
/
a -= 1
pero la expresión misma todavía tiene el valor anterior de a
.
Por el contrario, --a
y ++a
tienen el mismo efecto sobre a
pero retorna el valor después del cambio.
delete o eliminar¶
delete a
asigna el valor inicial para el tipo en a
.
Es decir, para enteros es equivalente a a = 0
,
pero también se puede utilizar en arrays, donde asigna una matriz dinámica de longitud cero o una matriz estática
de la misma longitud con todos los elementos restablecidos. Para structs, asigna una estructura con todos los miembros restablecidos.
delete
has no effect on whole mappings (as the keys of mappings may be arbitrary and are generally unknown).
So if you delete a struct, it will reset all members that are not mappings and also recurse into the
members unless they are mappings. However, individual keys and what they map to can be deleted.
no tiene ningún efecto en las asignaciones completas (ya que las claves de las asignaciones pueden ser
arbitrarias y generalmente son desconocidas). Así que si eliminas una estructura, restablecerá todos
los miembros que no sean asignaciones y también recursar en los miembros a menos que sean asignaciones.
Sin embargo, las claves individuales y lo que se asignan a se pueden eliminar.
Es importante notar que delete a
realmente se comporta como una asignación a
, es decir, almacena un nuevo objeto en
a
.
pragma solidity ^0.4.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() {
uint x = data;
delete x; // establece x en 0, no afecta data
delete data; // establece a data en 0, no afecta a x que todavía tiene una copia
uint[] y = dataArray;
delete dataArray; // esto establece dataArray.length a cero, pero como uint [] es un objeto complejo, también
// se afecta a "y" es un alias al objeto de almacenamiento
// Por otro lado: "delete y" no es válido, como asignaciones a variables locales
// referenciar objetos de almacenamiento sólo puede hacerse a partir de objetos de almacenamiento existentes.
}
}
Conversiones entre tipos elementales¶
Conversiones implícitas¶
Si un operador se aplica a diferentes tipos, el compilador intenta convertir implícitamente
uno de los operandos al tipo de otro (lo mismo es cierto para las asignaciones). En general,
una conversión implícita entre el valor de tipo es posible si tiene sentido semántico y no
se pierde información:
uint8
es convertible a
uint16
y int128
a int256
, pero int8
no es convertible a uint256
(porque uint256
no lo soporta por ejemplo
-1
).
Además, los enteros sin signo se pueden convertir en bytes del mismo tamaño o mayor, pero no viceversa.
Cualquier tipo que se puede convertir a uint160
también se puede convertir a address
.
Conversiones explícitas¶
Si el compilador no permite la conversión implícita pero sabe lo que está haciendo,
a veces es posible una conversión de tipo explícita. Tenga en cuenta que esto puede
darle un comportamiento inesperado así que asegúrese de probar para asegurarse de
que el resultado es lo que desea! Tome el ejemplo siguiente en el que está
convirtiendo un negativo int8
en un
uint
:
int8 y = -3;
uint x = uint(y);
Al final de este fragmento de código, x
tendrá el valor 0xfffff..fd
(64 caracteres hexadecimales), que es -3 en la representación del complemento de dos de 256 bits.
Si un tipo se convierte explícitamente en un tipo más pequeño, los bits de orden superior se cortan:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b será 0x5678 ahora
Tipos de Deduccion¶
Por comodidad, no siempre es necesario especificar explícitamente el tipo de una variable, el compilador lo infiere automáticamente del tipo de la primera expresión asignada a la variable:
uint24 x = 0x123;
var y = x;
Aquí, el tipo de y
será uint24
.
El uso de var
no es posible para parámetros de función o parámetros de retorno.
Advertencia
El tipo sólo se deduce de la primera asignación, por lo que el bucle en el fragmento siguiente es infinito, ya que
i
tendrá el tipo
uint8
y cualquier valor de este tipo es menor que
2000
.
for (var i = 0; i < 2000; i++) { ... }
Unidades y variables disponibles globalmente¶
Unidades de Éther¶
Un número literal puede tomar un sufijo de wei
,
finney
, szabo
o ether
para convertirse entre las subdenominaciones de ether, donde los números de moneda Ether sin un sufijo seran por defecto Wei, por ejemplo,
2 ether == 2000
finney
es evaluado a true
.
Unidades de tiempo¶
Sufijos como seconds
, minutes
,
hours
, days
,
weeks
y
years
después de los números literales se pueden utilizar para convertir entre unidades de tiempo
donde segundos son la unidad base y las unidades se consideran nativamente de la siguiente forma:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
1 years == 365 days
Tenga cuidado si realiza cálculos de calendario usando estas unidades, porque no todos los años equivale a 365 días y ni siquiera cada día tiene 24 horas debido a segundos de salto. Debido al hecho de que los segundos de salto no se pueden predecir, una biblioteca de calendario exacta tiene que ser actualizada por un oráculo externo.
Estos sufijos no se pueden aplicar a las variables. Si desea interpretar alguna variable de entrada, por ejemplo, días, puede hacerlo de la siguiente manera:
function f(uint start, uint daysAfter) {
if (now >= start + daysAfter * 1 days) {
// ...
}
}
Variables especiales y funciones¶
Hay variables y funciones especiales que siempre existen en el espacio de nombres global y se utilizan principalmente para proporcionar información sobre la blockchain.
Propiedades de bloques y transacciones¶
block.blockhash(uint blockNumber) returns (bytes32)
: hash del bloque dado - sólo funciona para los 256 bloques más recientes excluyendo el actualblock.coinbase
(address
): la dirección actual del minero del bloqueblock.difficulty
(uint
): dificultad de bloque actualblock.gaslimit
(uint
): limite de gas del bloque actualblock.number
(uint
): número de bloque actualblock.timestamp
(uint
): timestamp del bloque actual como segundos desde la época de unixmsg.data
(bytes
): calldata completomsg.gas
(uint
): gas residualmsg.sender
(address
): remitente del mensaje (llamada actual)msg.sig
(bytes4
): primeros cuatro bytes de los calldata (es decir, identificador de función)msg.value
(uint
): número de wei enviado con el mensajenow
(uint
): marca de tiempo de bloque actual (alias parablock.timestamp
)tx.gasprice
(uint
): precio del gas de la transaccióntx.origin
(address
): remitente de la transacción (llamada de cadena completa)
Nota
Los valores de todos los miembros de msg
,
incluyendo msg.sender
y
msg.value
pueden cambiar para cada llamada de función external.
Esto incluye llamadas a funciones de biblioteca.
Nota
Si desea implementar restricciones de acceso en las funciones de la biblioteca utilizando
msg.sender
, tiene que suministrar manualmente el valor de
msg.sender
como un argumento.
Nota
Los hashes de bloques no están disponibles para todos los bloques por razones de escalabilidad. Sólo puede acceder a los hashes de los últimos 256 bloques, todos los demás valores serán cero.
Manejo de errores¶
assert(bool condition)
:- Se arroja si la condición no se cumple - para ser utilizado para errores internos.
require(bool condition)
:- Se arroja si la condición no se cumple - para ser utilizado para errores en entradas o componentes externos.
revert()
:- cancelar la ejecución y revertir los cambios de estado
Funciones matemáticas y criptográficas¶
addmod(uint x, uint y, uint k) returns (uint)
:- calcula
(x + y) % k
donde la adición se realiza con precisión arbitraria y no se envuelve en2**256
. mulmod(uint x, uint y, uint k) returns (uint)
:- calcula
(x * y) % k
donde la multiplicación se realiza con precisión arbitraria y no se envuelve en2**256
. keccak256(...) returns (bytes32)
:- calcula el hash Ethereum-SHA-3 (Keccak-256) de los argumentos (tightly packed o firmemente empacados)
sha256(...) returns (bytes32)
:- calcula el hash SHA-256 de los argumentos (tightly packed o firmemente empacados)
sha3(...) returns (bytes32)
:- alias to
keccak256
ripemd160(...) returns (bytes20)
:- calcular RIPEMD-160 hash de los argumentos (tightly packed o firmemente empacados)
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:- recuperar la dirección asociada a la clave pública de la firma de la curva elíptica o devolver el cero en caso de error (ejemplo de uso)
En lo anterior, “tightly packed” (traduccion: hermeticamente empacados o firmemente empacados o apretado) significa que los argumentos se concatenan sin relleno. Esto significa que los siguientes son todos idénticos:
keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)
Si se necesita relleno, pueden usarse conversiones de tipos explícitas: keccak256("\x00\x12")
es igual que keccak256(uint16(0x12))
.
Tenga en cuenta que las constantes se empaquetarán utilizando el número mínimo de bytes necesarios para almacenarlas. Esto significa que, por ejemplo,
keccak256(0) == keccak256(uint8(0))
y
keccak256(0x12345678) == keccak256(uint32(0x12345678))
.
Puede ser que te encuentres con Out-of-Gas para sha256
,
ripemd160
o ecrecover
en una private blockchain. La razón de esto es que estos se implementan como los llamados contratos precompilados y
estos contratos sólo existen realmente después de que recibieron el primer mensaje
(aunque su código de contrato es codificado en tiempo real). Los mensajes a contratos
no existentes son más caros y por lo tanto la ejecución se ejecuta en un error
Out-of-Gas. Una solución para este problema es enviar primero por ejemplo 1 Wei a cada uno
de los contratos antes de utilizarlos en sus contratos reales.
Esto no es un problema en la red oficial o de prueba.
Expresiones y estructuras de control¶
Parámetros de entrada y parámetros de salida¶
Como en Javascript, las funciones pueden tomar parámetros como entrada; a diferencia de Javascript y C, también pueden devolver un número arbitrario de parámetros como salida.
Parámetros de entrada¶
Los parámetros de entrada se declaran de la misma manera que las variables. Como excepción, los parámetros no utilizados pueden omitir el nombre de la variable. Por ejemplo, supongamos que queremos que nuestro contrato acepte un tipo de llamadas externas con dos enteros, escribiríamos algo como:
pragma solidity ^0.4.0;
contract Simple {
function taker(uint _a, uint _b) {
// hacer algo con _a y _b.
}
}
Parámetros de salida¶
Los parámetros de salida se pueden declarar con la misma sintaxis después de la palabra clave
returns
.
Por ejemplo, supongamos que deseamos devolver dos resultados: la suma y el producto de los dos enteros dados, entonces escribiríamos:
pragma solidity ^0.4.0;
contract Simple {
function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
o_product = _a * _b;
}
}
Los nombres de los parámetros de salida pueden omitirse.
Los valores de salida también se pueden especificar mediante sentencias return
.
Las sentencias return
también son capaces de devolver varios valores, véase
Retorno de varios valores.
Los parámetros de retorno se inicializan a cero; si no se establecen explícitamente, permanecen cero.
Los parámetros de entrada y de salida se pueden utilizar como expresiones en el cuerpo de la función. Allí, también se pueden utilizar en el lado izquierdo de la asignación.
Estructuras de Control¶
La mayoría de las estructuras de control de JavaScript están disponibles en Solidity excepto para
switch
y goto
.
Entonces tenemos: if
, else
,
while
, do
,
for
, break
,
continue
, return
,
? :
,
con la semántica usual conocidos de C o JavaScript.
Los paréntesis no se pueden omitir para los condicionales, pero los bordes rizados se pueden omitir alrededor de los cuerpos de una sola afirmación.
Tenga en cuenta que no hay conversión de tipos no booleanos a tipos booleanos, que existe en C y JavaScript, entonces
if (1) { ... }
no es valido en Solidity.
Devolución de múltiples valores ¶
Cuando una función tiene varios parámetros de salida, return
(v0, v1, ...,vn)
puede devolver varios valores. El número de componentes debe ser el mismo que el número de parámetros de salida.
Llamadas de funciónes¶
Llamadas de función interna¶
Las funciones del contrato actual pueden ser llamadas directamente (“internamente”), también recursivamente, como se ve en este absurdo ejemplo:
pragma solidity ^0.4.0;
contract C {
function g(uint a) returns (uint ret) { return f(); }
function f() returns (uint ret) { return g(7) + f(); }
}
Estas llamadas de función se traducen en saltos simples dentro del EVM. Esto tiene el efecto de que la memoria actual no se borra, es decir, transferir referencias de memoria a funciones internamente llamadas es muy eficiente. Sólo las funciones del mismo contrato pueden ser llamadas internamente.
Llamadas de función externa¶
Las expresiones this.g(8);
y
c.g(2);
(donde c
es una instancia de contrato) son también llamadas de función válidas, pero esta vez, la función se llamará “externamente”,
a través de una llamada de mensaje y no directamente a través de saltos. Tenga en cuenta que las llamadas de función
this
no se pueden utilizar en el constructor, ya que el contrato real todavía no se ha creado.
Las funciones de otros contratos tienen que ser llamadas externamente. Para una llamada externa, todos los argumentos de función tienen que ser copiados en la memoria.
Cuando llamamos a funciones de otros contratos, la cantidad de Wei enviada con la llamada y el gas se puede especificar con las opciones especiales
.value()
y .gas()
, respectivamente:
pragma solidity ^0.4.0;
contract InfoFeed {
function info() payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) { feed = InfoFeed(addr); }
function callFeed() { feed.info.value(10).gas(800)(); }
}
El modificador payable
tiene que ser usado para info
, porque de lo contrario, la opción .value() no estaría disponible.
Tenga en cuenta que la expresión InfoFeed(addr)
realiza una conversión de tipo explícita indicando que “sabemos que el tipo del contrato en la dirección dada es
InfoFeed
”
y esto no ejecuta un constructor. Las conversiones de tipo explícito tienen que ser manejadas con extrema precaución.
Nunca llame a una función en un contrato en el que no esté seguro acerca de su tipo.
También podríamos haber usado directamente function setFeed(InfoFeed _feed)
{ feed = _feed; }
Tenga cuidado con el hecho de que feed.info.value(10).gas(800)
sólo (localmente) establece el valor y la cantidad de gas enviado con la llamada de función y
sólo los paréntesis al final realizan la llamada real.
Las llamadas de función provocan excepciones si el contrato llamado no existe (en el sentido de que la cuenta no contiene código) o si el contrato llamado mismo arroja una excepción o arroja out of gas.
Advertencia
Cualquier interacción con otro contrato impone un peligro potencial, especialmente si el código fuente del contrato no se conoce de antemano. El contrato actual entrega el control al contrato llamado y que potencialmente puede hacer cualquier cosa. Incluso si el contrato llamado hereda de un contrato padre conocido, el contrato hereditario sólo se requiere para tener una interfaz correcta. La ejecución del contrato, sin embargo, puede ser completamente arbitraria y, por lo tanto, suponen un peligro. Además, esté preparado en caso de que llame a otros contratos de su sistema o incluso de nuevo en el contrato de llamada antes de la primera llamada vuelve. Esto significa que el contrato llamado puede cambiar variables de estado del contrato de llamada a través de sus funciones. Escriba sus funciones de una manera que, por ejemplo, las llamadas a funciones externas ocurran después de cualquier cambio en las variables de estado en su contrato por lo que su contrato no es vulnerable a una explotación de reentrada.
Llamadas Nombradas y Parámetros de Función Anónima¶
Los argumentos de llamada de función también pueden darse por nombre, en cualquier orden, si están encerrados en
{ }
como se puede ver en el ejemplo siguiente. La lista de argumentos tiene que coincidir
por nombre con la lista de parámetros de la declaración de función, pero puede estar en orden arbitrario.
pragma solidity ^0.4.0;
contract C {
function f(uint key, uint value) {
// ...
}
function g() {
// argumentos nombrados
f({value: 2, key: 3});
}
}
Nombres de parámetros de función omitidos¶
Los nombres de los parámetros no utilizados (especialmente los parámetros de retorno) se pueden omitir. Esos nombres todavía estarán presentes en la pila, pero son inaccesibles.
pragma solidity ^0.4.0;
contract C {
// nombre omitido para el parámetro
function func(uint k, uint) returns(uint) {
return k;
}
}
Creación de contratos a través de new
¶
Un contrato puede crear un nuevo contrato utilizando la palabra clave new
.
El código completo del contrato que se crea tiene que ser conocido de antemano, por lo que no son posibles las dependencias recursivas de la creación.
pragma solidity ^0.4.0;
contract D {
uint x;
function D(uint a) payable {
x = a;
}
}
contract C {
D d = new D(4); // se ejecutará como parte del constructor de C
function createD(uint arg) {
D newD = new D(arg);
}
function createAndEndowD(uint arg, uint amount) {
// Envía ether junto con la creación
D newD = (new D).value(amount)(arg);
}
}
Como se ve en el ejemplo, es posible reenviar Ether a la creación utilizando la opción
.value()
, pero no es posible limitar la cantidad de gas. Si la creación falla (debido a la falta
de pila, no hay suficiente balance u otros problemas), se genera una excepción.
Orden de Evaluación de las Expresiones¶
No se especifica el orden de evaluación de las expresiones (más formalmente, no se especifica el orden en que se evalúan los hijos de un nodo en el árbol de expresiones, pero se evalúan antes del propio nodo). Sólo se garantiza que las sentencias se ejecutan en orden y se realiza un cortocircuito para las expresiones booleanas. Para más información ver Orden de Precedencia de Operadores.
Asignaciones¶
Desestructuración de asignaciones y devolución de valores múltiples¶
Solidity internamente permite tipos de tupla, es decir, una lista de objetos de tipos potencialmente diferentes cuyo tamaño es una constante en tiempo de compilación. Esas tuplas pueden usarse para devolver múltiples valores al mismo tiempo y también asignarlos a múltiples variables (o LValues en general) al mismo tiempo:
pragma solidity ^0.4.0;
contract C {
uint[] data;
function f() returns (uint, bool, uint) {
return (7, true, 2);
}
function g() {
// Declara y asigna las variables. Especificar el tipo explícitamente no es posible.
var (x, b, y) = f();
// Asigna a una variable preexistente.
(x, y) = (2, 7);
// Truco común para intercambiar valores - no funciona para tipos de almacenamiento sin valor.
(x, y) = (y, x);
// Los componentes se pueden omitir (también para las declaraciones de variables).
// Si la tupla termina en un componente vacío,
// el resto de los valores se descartan.
(data.length,) =
f(); // Establece la longitud a 7
// Lo mismo se puede hacer en el lado izquierdo.
(,data[3]) = f(); // Sets data[3] to 2
// Los componentes sólo se pueden omitir a la izquierda de las asignaciones, con
// una excepcion:
(x,) = (1,);
// ((1,) es la única manera de especificar una tupla de 1 componente, porque (1) es
// equivalente a 1.
}
}
Complicaciones para Arrays y Structs¶
La semántica de la asignación es un poco más complicada para los tipos sin valor como arrays y structs. Asignar a (to)
una variable de estado siempre crea una copia independiente. Por otra parte, asignar a una variable local crea
una copia independiente sólo para tipos elementales, es decir, tipos estáticos que encajan en 32 bytes. Si las estructuras o matrices (incluyendo
bytes
y string
)
se asignan desde una variable de estado a una variable local, la variable local contiene una referencia a la variable de estado original.
Una segunda asignación a la variable local no modifica el estado sino que sólo cambia la referencia. Las asignaciones a los miembros
(o elementos) de la variable local hacen (do) cambiar el estado.
Determinación del alcance y declaraciones¶
Una variable que se declara tendrá un valor inicial por defecto cuya representación de bytes es todos ceros. Los
“default values” de las variables son el típico “zero-state” de cualquiera que sea el tipo. Por ejemplo, el valor predeterminado de un
bool
es false
.
El valor predeterminado para los tipos uint
o int
es
0
. Para matrices de tamaño estático y bytes1
a bytes32
,
cada elemento individual se inicializará al valor por defecto correspondiente a su tipo. Por último, para matrices de tamaño dinámico,
bytes
y string
,
el valor predeterminado es una matriz o cadena vacía.
Una variable declarada en cualquier lugar dentro de una función estará en alcance para toda la función,
independientemente de dónde se declare. Esto sucede porque Solidity hereda sus reglas de alcance de JavaScript.
Esto contrasta con muchos lenguajes en los que las variables sólo tienen un ámbito en el que se declaran hasta
el final del bloque semántico. Como resultado, el código siguiente es ilegal y provoca que el compilador lance
un error,
Identifier already declared
:
// Esto no compilará
pragma solidity ^0.4.0;
contract ScopingErrors {
function scoping() {
uint i = 0;
while (i++ < 1) {
uint same1 = 0;
}
while (i++ < 2) {
uint same1 = 0;
// Ilegal, segunda declaración del mismo1
}
}
function minimalScoping() {
{
uint same2 = 0;
}
{
uint same2 = 0;
// Ilegal, segunda declaración del mismo2
}
}
function forLoopScoping() {
for (uint same3 = 0; same3 < 1; same3++) {
}
for (uint same3 = 0; same3 < 1; same3++) {
// Ilegal, segunda declaración del mismo3
}
}
}
Además de esto, si se declara una variable, se inicializará al principio de la función a su valor predeterminado. Como resultado, el siguiente código es legal, a pesar de estar mal escrito:
function foo() returns (uint) {
// baz se inicializa implícitamente como 0
uint bar = 5;
if (true) {
bar += baz;
} else {
uint baz = 10;
// nunca se ejecuta
}
return bar;// devuelve 5
}
Manejo de errores: Asertar, Requerir, Revertir y Excepciones (Assert, Require, Revert, Exceptions)¶
Solidity utiliza excepciones que revertirán el estado para manejar errores. Tal excepción deshará todos los cambios realizados al estado en la llamada actual
(y todas sus sub-llamadas) y también marcará un error al llamador. Las funciones
assert
y require
puede usarse a conveniencia para verificar condiciones y lanzar una excepción si la condición no se cumple. La función
assert
sólo debe utilizarse para comprobar errores internos y para comprobar invariantes. La función
require
debe utilizarse para garantizar que se cumplan condiciones válidas, como entradas o variables de estado de contrato, o para validar valores de retorno
de llamadas a contratos externos. Si se usan correctamente, las herramientas de análisis pueden evaluar su contrato para identificar las condiciones y
las llamadas de función que llegarán a un failing
assert
.
El código de funcionamiento correcto nunca debe llegar a una sentencia afirmativa fallida; si esto ocurre hay un error en su contrato que debe arreglar.
Existen otras dos formas de activar excepciones: La función revert
se puede utilizar para marcar un error y revertir la llamada actual. En el futuro también podría ser posible incluir detalles sobre el error en una llamada a
revert
. La palabra clave throw
también se puede utilizar como una alternativa a revert()
.
Nota
Desde la versión 0.4.13 la palabra clave throw
está obsoleta y se eliminará gradualmente en el futuro.
Cuando las excepciones ocurren en una sub-llamada, ellas burbujean o “bubble up”
(es decir, las excepciones se vuelven a disparar) de forma automática. Excepciones a esta regla son
send
y las funciones de bajo nivel call
,
delegatecall
y callcode
– las devuelven false
en caso de una excepción en lugar de burbujeo o “bubbling up”.
Advertencia
El nivel bajo call
,
delegatecall
y callcode
retornará success si la cuenta de llamada no existe, como parte del diseño de EVM. La existencia debe ser verificada antes de llamar si lo desea.
La captura de excepciones aún no es posible.
En el siguiente ejemplo, puede ver cómo require
se puede utilizar para verificar fácilmente las condiciones de las entradas y cómo
assert
se puede utilizar para la comprobación interna de errores:
pragma solidity ^0.4.0;
contract Sharer {
function sendHalf(address addr) payable returns (uint balance) {
require(msg.value % 2 == 0);
// Sólo permite números pares
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
// Dado que la transferencia genera una excepción en caso de fallo y
// no puede volver a llamar aquí, no debería haber manera para nosotros de
// tener todavía la mitad del dinero.
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
Una excepción de estilo assert
se genera en las siguientes situaciones:
- Si accede a una matriz en un índice demasiado grande o negativo (es decir,
x[i]
dondei >= x.length
oi < 0
). - Si accede a una longitud fija
bytesN
en un índice demasiado grande o negativo. - Si divide o modulo por cero (e.g.
5 / 0
o23 % 0
). - Si cambia "shift" por una cantidad negativa.
- Si convierte un valor demasiado grande o negativo en un tipo de enum.
- Si llama a una variable de inicialización cero de tipo de función interna.
- Si llama a
assert
con un argumento que se evalúa como false.
Una excepción de estilo require
se genera en las siguientes situaciones:
- Llamando
throw
. - Llamando
require
con un argumento que se evalúa comofalse
. - Si se llama a una función a través de una llamada mensaje, pero no termina correctamente
(es decir, que se queda sin gas, no tiene ninguna función de coincidencia, o produce una excepción en sí), excepto cuando se utiliza una operación de bajo nivel
call
,send
,delegatecall
ocallcode
. Las operaciones de bajo nivel nunca lanzan excepciones pero indican fallas al retornarfalse
. - Si crea un contrato utilizando la palabra clave
new
pero la creación del contrato no finaliza correctamente (véase más arriba para la definición de “not finish properly”). - Si realiza una llamada de función externa dirigida a un contrato que no contiene código.
- Si su contrato recibe Ether a través de una función pública sin modificador
payable
(incluyendo el constructor y la función de fallback). - Si su contrato recibe Ether a través de una función getter pública.
- Si falla un
.transfer()
.
Internamente, Solidity realiza una operación de revertir (instrucción 0xfd
) para una excepción de estilo
require
y ejecuta una operación no válida (instrucción
0xfe
) para lanzar una excepción de estilo assert
En ambos casos, esto hace al EVM revertir todos los cambios realizados en el estado.
La razón para revertir es que no hay manera segura de continuar la ejecución, porque no se produjo un efecto esperado.
Debido a que queremos retener la atomicidad de las transacciones, lo más seguro es revertir todos los cambios y hacer
que toda la transacción (o al menos call) quede sin efecto. Tenga en cuenta que las excepciones de estilo
assert
consumen todo el gas disponible para la llamada, mientras que las excepciones de estilo
require
no consumirán ningún gas a partir de la liberación de Metropolis.
Contratos¶
Los Contratos en Solidity son similares a las clases en lenguajes orientados a objetos. Contienen datos persistentes en variables de estado y funciones que pueden modificar estas variables. Llamar a una función en un contrato diferente (instancia) realizará una llamada de función EVM y, por tanto, cambiará el contexto de modo que las variables de estado sean inaccesibles.
Creación de Contratos¶
Los contratos pueden ser creados "desde fuera" o desde contratos de Solidity. Cuando se crea un contrato, su constructor (una función con el mismo nombre que el contrato) se ejecuta una vez.
Un constructor es opcional. Sólo se permite un constructor, y esto significa que la sobrecarga no es compatible.
Desde web3.js
, es decir, la API de JavaScript, esto se hace de la siguiente manera:
// Necesita especificar alguna fuente incluyendo el nombre del contrato para el parámetro de datos debajo
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";
// El json array abi generado por el compilador
var abiArray = [
{
"inputs":[
{"name":"x","type":"uint256"},
{"name":"y","type":"uint256"}
],
"type":"constructor"
},
{
"constant":true,
"inputs":[],
"name":"x",
"outputs":[{"name":"","type":"bytes32"}],
"type":"function"
}
];
var MyContract_ = web3.eth.contract(source);
MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition);
// desplegar nuevo contrato
var contractInstance = MyContract.new(
10,
11,
{from: myAccount, gas: 1000000}
);
Internamente, los argumentos del constructor se pasan después del código del contrato en sí, pero usted no tiene que preocuparse por esto si usa web3.js
.
Si un contrato quiere crear otro contrato, el código fuente (y el binario) del contrato creado debe ser conocido por el creador. Esto significa que las dependencias de creación cíclica son imposibles.
pragma solidity ^0.4.0;
contract OwnedToken {
// TokenCreator es un tipo de contrato que se define a continuación.
// Está bien hacer referencia siempre y cuando no se use
// para crear un nuevo contrato.
TokenCreator creator;
address owner;
bytes32 name;
// Este es el constructor que registra el
// creador y el nombre asignado.
function OwnedToken(bytes32 _name) {
// Se accede a las variables de estado a través de su nombre
// y no a través de, por ejemplo, this.owner. Esto también se aplica
// a las funciones y especialmente en los constructores,
// sólo se puede llamar así ("internamente"), ,
// porque el contrato en sí no existe todavía.
owner = msg.sender;
// Hacemos una conversión de tipo explícita de `address`
// ta` TokenCreator` y asumimos que el tipo de
// el contrato que llama es TokenCreator,
// no hay manera real de comprobar eso.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) {
// Sólo el creador puede alterar el nombre --
// la comparación es posible ya que los contratos
// son implícitamente convertibles en direcciones.
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) {
// Sólo el propietario actual puede transferir el token.
if (msg.sender != owner) return;
// También queremos preguntar al creador si la transferencia
// está bien. Tenga en cuenta que esto llama una función del
// contrato definido a continuación. Si la llamada falla (por ejemplo,
// debido a la falta de gas), la ejecución aquí se detiene
// inmediatamente.
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
returns (OwnedToken tokenAddress)
{
// Crea un nuevo contrato Token y devuelve su dirección.
// Del lado de JavaScript, el tipo de retorno es simplemente
// "address", ya que éste es el tipo más cercano disponible en
// el ABI.
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) {
// De nuevo, el tipo externo de "tokenAddress" es
// simplemente "address".
tokenAddress.changeName(name);
}
function isTokenTransferOK(
address currentOwner,
address newOwner
) returns (bool ok) {
// Check some arbitrary condition.
address tokenAddress = msg.sender;
return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
}
}
Visibilidad y Getters¶
Dado que Solidity conoce dos tipos de llamadas de función (las internas que no crean una llamada EVM real (también llamada una “message call o llamada de mensaje”) y las externas que lo hacen), hay cuatro tipos de visibilidades para funciones y variables de estado.
Las funciones pueden ser especificadas como tipo external
,
public
, internal
o private
, donde el valor predeterminado es
public
. Para las variables de estado,
external
no es posible y el valor predeterminado es
internal
.
external
:- Las funciones externas forman parte de la interfaz del contrato,
lo que significa que pueden ser llamadas desde otros contratos y
por medio de transacciones. Una función externa
f
no se puede llamar internamente (es decirf()
no funciona, perothis.f()
si funciona). Las funciones externas son a veces más eficientes cuando reciben grandes arreglos de datos. public
:- Las funciones públicas forman parte de la interfaz del contrato y pueden ser llamadas internamente o por mensajes. Para las variables de estado público, se genera una función getter automática (véase más adelante).
internal
:- Esas funciones y variables de estado sólo se pueden acceder internamente
(es decir, de dentro del contrato o contratos actuales derivados de él), sin utilizar
this
. private
:- Las funciones privadas y las variables de estado sólo son visibles para el contrato en el que se definen y no en los contratos derivados.
Nota
Todo lo que está dentro de un contrato es visible para todos los observadores externos. Hacer algo
private
sólo impide que otros contratos accedan y modifiquen la información,
pero seguirá siendo visible para todo el mundo fuera de la blockchain.
El especificador de visibilidad se da después del tipo de variables de estado y entre la lista de parámetros y la lista de parámetros de retorno para las funciones.
pragma solidity ^0.4.0;
contract C {
function f(uint a) private returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
En el siguiente ejemplo, D
, puede llamar
c.getData()
para recuperar el valor de
data
en almacenamiento de estado, pero no puede llamar
f
. El contrato E
es derivado de
C
y por lo tanto, puede llamar compute
.
// Esto no compilará
pragma solidity ^0.4.0;
contract C {
uint private data;
function f(uint a) private returns(uint b) { return a + 1; }
function setData(uint a) { data = a; }
function getData() public returns(uint) { return data; }
function compute(uint a, uint b) internal returns (uint) { return a+b; }
}
contract D {
function readData() {
C c = new C();
uint local = c.f(7); // error: el miembro "f" no es visible
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // error: el miembro "compute" no es visible
}
}
contract E is C {
function g() {
C c = new C();
uint val = compute(3, 5); // acceso al miembro interno (del contrato derivado al contrato principal)
}
}
Funciones Getter¶
El compilador crea automáticamente las funciones getter para todas las variables de estado public.
Para el contrato dado a continuación, el compilador generará una función llamada
data
que no toma argumentos y devuelve a
uint
, el valor de la variable de estado
data
. La inicialización de las variables de estado se puede hacer en la declaración.
pragma solidity ^0.4.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() {
uint local = c.data();
}
}
Las funciones getter tienen visibilidad externa. Si el símbolo se accede internamente (es decir, sin
this.
),
se evalúa como una variable de estado. Si se accede externamente (es decir, con
this.
), se evalúa como una función.
pragma solidity ^0.4.0;
contract C {
uint public data;
function x() {
data = 3; // acceso interno
uint val = this.data(); // acceso externo
}
}
El siguiente ejemplo es un poco más complejo:
pragma solidity ^0.4.0;
contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
}
Se generará una función de la siguiente forma:
function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b) {
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
}
Tenga en cuenta que la asignación en la estructura se omite porque no hay ninguna buena forma de proporcionar la clave para la asignación(mapping) .
Modificadores de funciones¶
Los modificadores se pueden utilizar para cambiar fácilmente el comportamiento de las funciones. Por ejemplo, pueden comprobar automáticamente una condición antes de ejecutar la función. Los modificadores son propiedades heredables de los contratos y pueden ser anulados por los contratos derivados.
pragma solidity ^0.4.11;
contract owned {
function owned() { owner = msg.sender; }
address owner;
// Este contrato sólo define un modificador pero no lo usa
// it - se utilizará en contratos derivados.
// Se inserta el cuerpo de la función donde está el símbolo especial
// en la definición de un modificador aparece "_;" .
// Esto significa que si el propietario llama a esta función,
// se ejecuta la función y, de lo contrario, se produce
// una excepción.
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
contract mortal is owned {
// Este contrato hereda el modificador "onlyOwner" de
// "owned" y lo aplica a la función "close",que
// hace que las llamadas a "close" solo tengan un efecto si
// son realizadas por el propietario almacenado.
function close() onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Los modificadores pueden recibir argumentos:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
function Register(uint initialPrice) { price = initialPrice; }
// Es importante también proporcionar la
// palabra clave "payable" aquí, de lo contrario la función
// rechazará automáticamente todo el ether que se le envíe.
function register() payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) onlyOwner {
price = _price;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
/// Esta función está protegida por un mutex, lo que significa que
/// llamadas reentrantes desde dentro de msg.sender.call no se puede llamar a f de nuevo.
/// La sentencia `return 7` asigna 7 al valor devuelto pero todavía
/// ejecuta la instrucción` locked = false` en el modificador.
function f() noReentrancy returns (uint) {
require(msg.sender.call());
return 7;
}
}
Se aplican múltiples modificadores a una función especificándolos en una lista separada por espacios en blanco y se evalúan en el orden presentado.
Advertencia
En una versión anterior de Solidity, las sentencias return
en funciones que tenían modificadores se comportaron de manera diferente.
Los retornos explícitos de un modificador o cuerpo de función sólo dejan el modificador actual o el cuerpo de la función. Las variables de retorno se asignan y el flujo de control continúa después del “_” en el modificador anterior.
Las expresiones arbitrarias se permiten para los argumentos modificadores y en este contexto, todos los símbolos visibles de la función son visibles en el modificador. Los símbolos introducidos en el modificador no son visibles en la función (ya que podrían cambiar por sobreescritura).
Variables de Estado Constante¶
Las variables de estado se pueden declarar como constant
.
En este caso, tienen que ser asignados a partir de una expresión que es una constante en tiempo de compilación.
Cualquier expresión que acceda a storage, blockchain data (e.g.
now
, this.balance
o
block.number
) o
datos de ejecución (msg.gas
)
o que realice llamadas a contratos externos no se permitirán. Las expresiones que podrían tener un efecto secundario
sobre la asignación de memoria son permitidas, pero no las que pueden tener un efecto secundario en otros objetos de
memoria. Las funciones integradas
keccak256
, sha256
,
ripemd160
, ecrecover
,
addmod
and mulmod
estan permitidas (a pesar de que ellos hacen llamar los contratos externos).
La razón detrás de permitir efectos secundarios en el asignador de memoria es que debería ser posible construir objetos complejos como, por ejemplo, lookup-tables. Esta característica aún no es totalmente utilizable.
El compilador no reserva una ranura de almacenamiento para estas variables, y cada ocurrencia es reemplazada por la expresión constante respectiva (que puede ser computada a un solo valor por el optimizador).
No todos los tipos de constantes se implementan en este momento. Los únicos tipos soportados son tipos de value y strings.
pragma solidity ^0.4.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
}
Funciones de visualización¶
Se pueden declarar las funciones view
en cuyo caso prometen no modificar el estado.
Las siguientes sentencias se consideran modificadores de estado:
- Escribir en variables de estado.
- Emisión de eventos..
- Creación de otros contratos.
- Usando
selfdestruct
. - Envío de ether a través de llamadas.
- Llamar a cualquier función no marcada
view
opure
. - Uso de llamadas de bajo nivel.
- Uso del ensamblado en línea que contiene ciertos opcodes.
pragma solidity ^0.4.16;
contract C {
function f(uint a, uint b) view returns (uint) {
return a * (b + 42) + now;
}
}
Nota
constant
es un alias a view
.
Note
Los métodos Getter están marcados view
.
Advertencia
El compilador no aplica aún que un método view
no esté modificando el estado.
Funciones puras¶
Se pueden declarar funciones pure
,en cuyo caso prometen no leer ni modificar el estado.
Además de la lista de estados de modificación explicada anteriormente, se consideran como lectura del estado:
- Lectura de variables de estado.
- Acceso
this.balance
o<address>.balance
. - Acceder a cualquiera de los miembros de
block
,tx
,msg
(con la excepción demsg.sig
ymsg.data
). - Llamar a cualquier función no marcada
pure
. - Uso del ensamblado en línea que contiene ciertos opcodes.
pragma solidity ^0.4.16;
contract C {
function f(uint a, uint b) pure returns (uint) {
return a * (b + 42);
}
}
Advertencia
El compilador no aplica todavia que un método pure
no esté leyendo desde el estado.
Función de retroceso (Fallback)¶
Un contrato puede tener exactamente una función sin nombre. Esta función no puede tener argumentos y no puede devolver nada. Se ejecuta en una llamada al contrato si ninguna de las otras funciones coincide con el identificador de función dado (o si no se ha proporcionado ningún dato en absoluto).
Además, esta función se ejecuta siempre que el contrato reciba Ether simple (sin datos). En este contexto, normalmente hay muy poco gas disponible para la llamada de función (para ser precisos, 2300 de gas), por lo que es importante que las funciones de fallback sean tan baratas como sea posible.
En particular, las siguientes operaciones consumirán más gas que el expendio proporcionado a una función de reserva:
- Escribir en el almacenamiento (Writing to storage)
- Creación de un contrato
- Llamar a una función externa que consume una gran cantidad de gas
- Enviando Ether
Por favor asegúrese de probar su función de fallback a fondo para asegurar que el costo de ejecución es menos de 2300 de gas antes de desplegar un contrato.
Advertencia
Los contratos que reciben Ether directamente (sin una llamada de función, es decir, usando
send
o transfer
)
pero no definen una función de fallback lanzan una excepción, devolviendo el ether
(esto era diferente antes de Solidity v0.4.0). Así que si quieres que tu contrato reciba Ether,
debes implementar una función de fallback..
pragma solidity ^0.4.0;
contract Test {
// Esta función se llama para todos los mensajes enviados a
// este contrato (no hay otra función).
// Enviar Ether a este contrato causará una excepción,
// porque la función fallback no tiene el modificador
// "payable".
function() { x = 1; }
uint x;
}
// Este contrato mantiene a todo Ether enviado a él sin ninguna manera
// para recuperarlo.
contract Sink {
function() payable { }
}
contract Caller {
function callTest(Test test) {
test.call(0xabcdef01); // hash does not exist
// resulta en test.x becoming == 1.
// Lo siguiente no compilará, pero incluso
// si alguien envía ether a ese contrato,
// la transacción fallará y rechazará
// el ether.
//test.send(2 ether);
}
}
Eventos¶
Los eventos permiten el uso conveniente de las instalaciones de registro de EVM, que a su vez se pueden utilizar para “llamar” callbacks de JavaScript en la interfaz de usuario de un dapp, que escucha estos eventos.
Los eventos son miembros heredables de los contratos. Cuando se llaman, hacen que los argumentos se almacenen en el registro de la transacción, una estructura de datos especial en la cadena de bloques. Estos registros están asociados con la dirección del contrato y se incorporarán a la cadena de bloques y permanecerán allí mientras el bloque sea accesible (siempre como Frontier y Homestead, pero esto puede cambiar con Serenity). Los datos de registros y eventos no son accesibles desde dentro de los contratos (ni siquiera del contrato que los creó).
Las pruebas de SPV para registros son posibles, por lo que si una entidad externa proporciona un contrato con dicha prueba, puede comprobar que el registro realmente existe dentro de la cadena de bloques. Pero tenga en cuenta que los encabezados de bloque tienen que ser suministrados porque el contrato sólo puede ver los últimos 256 hashes de bloque.
Hasta tres parámetros pueden recibir el atributo indexed
que hará que se busquen los respectivos argumentos: Es posible filtrar
para valores específicos de argumentos indexados en la interfaz de usuario.
Si arrays (incluidos string
y bytes
)
se utilizan como argumentos indexados, el hash Keccak-256 de él se almacena como tema en su lugar.
El hash de la firma del evento es uno de los temas excepto si declaró el evento con el especificador
anonymous
.
Esto significa que no es posible filtrar para eventos anónimos específicos por nombre.
Todos los argumentos no indexados se almacenarán en la parte de datos del registro (log).
Nota
Los argumentos indexados no se almacenarán ellos mismos. Sólo puede buscar los valores, pero es imposible recuperar los valores ellos mismos.
pragma solidity ^0.4.0;
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);
function deposit(bytes32 _id) payable {
// Cualquier llamada a esta función (incluso profundamente anidada) puede
// ser detectada a partir de la API JavaScript filtrando
// for `Deposit` to be called.
Deposit(msg.sender, _id, msg.value);
}
}
El uso en la API de JavaScript sería el siguiente:
var abi = /* abi generado por el compilador */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* direccion */);
var event = clientReceipt.Deposit();
// mirar cambios
event.watch(function(error, result){
// resultado contendrá información variada
// incluyendo los argumets dados a la consola de
// Deposits call.
if (!error)
console.log(result);
});
// O pasar una devolución de llamada (callback) para comenzar a ver inmediatamente
var event = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
Interfaz de bajo nivel con registros (logs)¶
También es posible acceder a la interfaz de bajo nivel para el mecanismo de registro a través de las funciones
log0
,
log1
, log2
, log3
and log4
.
logi
toma
i + 1
el parámetro de tipo bytes32
,
donde se utilizará el primer argumento para la parte de datos del registro y los demás como temas.
La llamada de evento anterior se puede realizar de la misma manera que
log3(
msg.value,
0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
msg.sender,
_id
);
donde el número hexadecimal largo es igual a la firma del evento
keccak256("Deposit(address,hash256,uint256)")
.
Recursos adicionales para entender los eventos¶
Herencia¶
Solidity admite múltiples herencias copiando el código, incluyendo el polimorfismo.
Todas las llamadas de función son virtuales, lo que significa que se llama a la función más derivada, excepto cuando el nombre del contrato se da explícitamente.
Cuando un contrato hereda de varios contratos, sólo se crea un contrato único en la cadena de bloque, y el código de todos los contratos base se copia en el contrato creado.
El sistema de herencia general es muy similar al de Python’s, especialmente en relación con la herencia múltiple.
Los detalles se dan en el siguiente ejemplo.
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// Use "is" para derivar de otro contrato. Los contratos derivados
// pueden acceder a todos los miembros no privados incluyendo
// funciones internas y variables de estado. Sin embargo,
// estos no se pueden acceder externamente a través de `this`.
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
// Estos contratos abstractos sólo se proporcionan para hacer que la
// interfaz sea conocida por el compilador. Observe la función
// sin cuerpo(body). Si un contrato no implementa todas las
// funciones, sólo puede utilizarse como una interfaz.
contract Config {
function lookup(uint id) returns (address adr);
}
contract NameReg {
function register(bytes32 name);
function unregister();
}
// Es posible la herencia múltiple. Tenga en cuenta que "owned" es
// también una clase base de "mortal", sin embargo, sólo hay una única
// instancia de "owned" (como para la herencia virtual en C ++).
contract named is owned, mortal {
function named(bytes32 name) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).register(name);
}
// Las funciones pueden ser anuladas por otra función con el mismo nombre y
// el mismo número / tipos de entradas. Si la función principal tiene diferentes
// tipos de parámetros de salida, esto causa un error.
// Tanto las llamadas de función locales como las basadas en mensajes toman
// en cuenta estas sustituciones.
function kill() {
if (msg.sender == owner) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).unregister();
// Todavía es posible llamar a una función específica
// overridden.
mortal.kill();
}
}
}
// Si un constructor toma un argumento, necesita ser
// proporcionado en el encabezado (o modificador-invocación-estilo en
// el constructor del contrato derivado (ver abajo)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) {
if (msg.sender == owner) info = newInfo;
}
function get() constant returns(uint r) { return info; }
uint info;
}
Tenga en cuenta que arriba, llamamos a mortal.kill()
a “forward”
(reenviar) la solicitud de destrucción. La manera en que esto se hace es problemática, como se ve en el siguiente ejemplo:
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
Una llamada a Final.kill()
llamará Base2.kill
como la derivación más derivada, pero esta función pasará por alto
Base1.kill
,
básicamente porque ni siquiera sabe acerca de
Base1
.
La manera alrededor de esto es utilizar super
:
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base2, Base1 {
}
Si Base1
calls a function of super
, it does not simply
llama una función de super, no simplemente llama a esta función en uno de sus contratos base.
Por el contrario, llama a esta función en el siguiente contrato base en el gráfico de herencia final,
por lo que llamará Base2.kill()
(nota que la secuencia de herencia final es - comenzando con el contrato más derivado:
Final, Base1, Base2, mortal, propiedad). La función real que se llama cuando se utiliza super
se desconoce en el contexto de la clase donde se utiliza, aunque su tipo es conocido.
Esto es similar para la búsqueda de métodos virtuales ordinarios.
Argumentos para los constructores de base¶
Los contratos derivados deben proporcionar todos los argumentos necesarios para los constructores de base. Esto se puede hacer de dos formas:
pragma solidity ^0.4.0;
contract Base {
uint x;
function Base(uint _x) { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) {
}
}
Una forma está directamente en la lista de herencia (is Base(7)
).
El otro está en la forma en que un modificador sería invocado como parte del encabezado del constructor derivado
(Base(_y * _y)
).
La primera manera de hacerlo es más conveniente si el argumento del constructor es una constante y
define el comportamiento del contrato o lo describe. La segunda forma tiene que ser utilizada si los
argumentos del constructor de la base dependen de los del contrato derivado. Si, como en este ejemplo
tonto, ambos lugares se usan, el argumento de estilo modificador tiene prioridad.
Herencia Múltiple y Linealización¶
Los lenguajes que permiten la herencia múltiple tienen que tratar con varios problemas. Uno es el
Diamond Problem.
Solidity sigue el camino de Python y utiliza “C3 Linearization”
para forzar un orden específico en el DAG de las clases base. Esto resulta en la propiedad deseable
de la monotonicidad, pero no permite algunos gráficos de herencia. Especialmente, el orden en que se
dan las clases base en la directiva is
es importante. En el siguiente código, Solidity dará el error “Linearization of inheritance graph impossible”.
// Esto no compilará
pragma solidity ^0.4.0;
contract X {}
contract A is X {}
contract C is A, X {}
La razón de esto es que las C
peticiones X
para anular A
(especificando A, X
en este orden), pero
A
sí las solicitudes para anular
X
, son una contradicción que no puede ser resuelto.
Una regla simple a recordar es especificar las clases base en el orden de “most base-like” a “most derived”.
Heredando diferentes tipos de miembros del mismo nombre¶
Cuando la herencia da como resultado un contrato con una función y un modificador del mismo nombre, se considera como un error. Este error es producido también por un evento y un modificador del mismo nombre, y una función y un evento del mismo nombre. Como excepción, un getter de variable de estado puede anular una función pública.
Contratos abstractos¶
Las funciones de contrato pueden carecer de una implementación como en el ejemplo siguiente
(tenga en cuenta que el encabezado de declaración de función es terminado por ;
):
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
}
Tales contratos no pueden ser compilados (aunque contengan funciones implementadas junto con funciones no implementadas), pero pueden ser utilizados como contratos base:
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
}
contract Cat is Feline {
function utterance() returns (bytes32) { return "miaow"; }
}
Si un contrato hereda de un contrato abstracto y no implementa todas las funciones no implementadas por sobreescribir, será por sí mismo abstracto.
Interfaces¶
Las interfaces son similares a los contratos abstractos, pero no pueden tener ninguna función implementada. Existen otras restricciones:
- No puede heredar otros contratos o interfaces.
- No se puede definir el constructor.
- No se pueden definir variables.
- No se pueden definir estructuras.
- No se pueden definir enums.
Algunas de estas restricciones podrían ser levantadas en el futuro.
Las interfaces se limitan básicamente a lo que el contrato ABI puede representar, y la conversión entre el ABI y una interfaz debe ser posible sin pérdida de información.
Las interfaces se indican con su propia palabra clave:
pragma solidity ^0.4.11;
interface Token {
function transfer(address recipient, uint amount);
}
Los contratos pueden heredar interfaces, ya que heredarían otros contratos.
Libraries o Bibliotecas¶
Las bibliotecas son similares a los contratos, pero su propósito es que se desplieguen sólo una vez en una dirección específica y
su código se reutilice utilizando la función DELEGATECALL
(CALLCODE
hasta Homestead)
del EVM. Esto significa que si se llaman las funciones de la biblioteca, su código se ejecuta en el contexto del contrato de llamada,
es decir, los puntos this
del contrato de llamada y, especialmente, el almacenamiento del contrato de llamada se puede acceder. Como una biblioteca
es una pieza aislada del código fuente, sólo puede acceder a las variables de estado del contrato de llamada si se suministran
explícitamente (no tendría forma de nombrarlos, de lo contrario).
Las bibliotecas pueden ser vistas como contratos base implícitos de los contratos que las utilizan. No serán explícitamente visibles
en la jerarquía de herencia, pero las llamadas a funciones de biblioteca se parecen a las llamadas a funciones de contratos base explícitos
(L.f()
si L
es el nombre de la biblioteca). Además, las funciones
internas
de las bibliotecas son visibles en todos los contratos, como si la biblioteca fuera un contrato base.
Por supuesto, las llamadas a funciones internas utilizan la convención de llamada interna, lo que significa
que se pueden pasar todos los tipos internos y los tipos de memoria se pasarán por referencia y no se copiarán.
Para realizar esto en el EVM, el código de las funciones internas de la biblioteca y todas las funciones llamadas a
partir de la misma se insertarán en el contrato de llamada
JUMP
y se utilizará una llamada regular en lugar de a
DELEGATECALL
.
El siguiente ejemplo ilustra cómo utilizar las bibliotecas (pero asegúrese de consultar using for para un ejemplo más avanzado para implementar un conjunto).
pragma solidity ^0.4.11;
library Set {
// Definimos un nuevo tipo de datos struct que se utilizará para
// mantener sus datos en el contrato de llamada.
struct Data { mapping(uint => bool) flags; }
// Tenga en cuenta que el primer parámetro es del tipo "storage
// reference" y por lo tanto sólo su dirección de almacenamiento y no
// su contenido se pasa como parte de la llamada. Esta es una
// característica especial de las funciones de la biblioteca. Es idiomático
// llamar al primer parámetro 'self', si la función
// puede ser vista como un método de ese objeto.
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // ya hay
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) {
// Las funciones de biblioteca se pueden llamar sin una
// instancia específica de la biblioteca, ya que
// "instance" será el contrato actual.
require(Set.insert(knownValues, value));
}
// En este contrato, también podemos acceder directamente a knownValues.flags, si queremos.
}
Por supuesto, usted no tiene que seguir esta manera de utilizar las bibliotecas - también pueden ser utilizados sin la definición de tipos de datos de estructura. Las funciones también funcionan sin ningún parámetro de referencia de almacenamiento, y pueden tener múltiples parámetros de referencia de almacenamiento y en cualquier posición.
Las llamadas a Set.contains
, Set.insert
y Set.remove
se compilan todos como calls (DELEGATECALL
)
a un contrato externo / biblioteca. Si utiliza bibliotecas, asegúrese de que se realice una llamada de función externa real.
msg.sender
, msg.value
y
this
conservarán sus valores en esta llamada, sin embargo (antes de Homestead, debido al uso de CALLCODE
, msg.sender
and
msg.value
cambia).
El siguiente ejemplo muestra cómo utilizar tipos de memoria y funciones internas en bibliotecas para implementar tipos personalizados sin la sobrecarga de las llamadas de función externas:
pragma solidity ^0.4.0;
library BigInt {
struct bigint {
uint[] limbs;
}
function fromUint(uint x) internal returns (bigint r) {
r.limbs = new uint[](1);
r.limbs[0] = x;
}
function add(bigint _a, bigint _b) internal returns (bigint r) {
r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
uint carry = 0;
for (uint i = 0; i < r.limbs.length; ++i) {
uint a = limb(_a, i);
uint b = limb(_b, i);
r.limbs[i] = a + b + carry;
if (a + b < a || (a + b == uint(-1) && carry > 0))
carry = 1;
else
carry = 0;
}
if (carry > 0) {
// too bad, we have to add a limb
uint[] memory newLimbs = new uint[](r.limbs.length + 1);
for (i = 0; i < r.limbs.length; ++i)
newLimbs[i] = r.limbs[i];
newLimbs[i] = carry;
r.limbs = newLimbs;
}
}
function limb(bigint _a, uint _limb) internal returns (uint) {
return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
}
function max(uint a, uint b) private returns (uint) {
return a > b ? a : b;
}
}
contract C {
using BigInt for BigInt.bigint;
function f() {
var x = BigInt.fromUint(7);
var y = BigInt.fromUint(uint(-1));
var z = x.add(y);
}
}
Dado que el compilador no puede saber dónde se desplegará la biblioteca, estas direcciones deben ser rellenadas en el
bytecode final por un vinculador (consulte
Utilización del compilador de línea de comandos
para saber cómo utilizar el compilador de línea de comandos para vincular). Si las direcciones no se dan como argumentos al compilador,
el código hexadecimal compilado contendrá marcadores de posición del formato
__Set______
(donde
Set
es el nombre de la biblioteca). La dirección se puede rellenar manualmente
reemplazando todos esos 40 símbolos por la codificación hexadecimal de la dirección del contrato de biblioteca.
Restricciones para las bibliotecas en comparación con los contratos:
- No hay variables de estado
- No puede heredar ni ser heredado
- No se puede recibir Ether
(Estos pueden ser levantados en un punto posterior.)
Using For¶
La directiva using A for
B;
se puede utilizar para conectar funciones de biblioteca (desde la biblioteca A
)
a cualquier tipo (B
).
Estas funciones recibirán el objeto al que son llamados como su primer parámetro (como la variable
self
en Python).
El efecto de using A for *;
es que las funciones de la biblioteca A
se adjuntan a cualquier tipo.
En ambas situaciones, todas las funciones, incluso aquellas en las que el tipo del primer parámetro no coincide con el tipo del objeto, se adjuntan. El tipo se comprueba en el punto en que se llama a la función y se realiza la resolución de sobrecarga de funciones.
La directiva using A for B;
está activa para el ámbito actual, que se limita a un contrato por ahora, pero se elevará al ámbito global más adelante, de modo que al incluir un módulo,
sus tipos de datos, incluidas las funciones de biblioteca, estén disponibles sin tener que añadir más código.
Vamos a reescribir el ejemplo de conjunto de las Libraries de esta manera:
pragma solidity ^0.4.11;
// Este es el mismo código que antes, solo sin comentarios
library Set {
struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // ya hay
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // no hay
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
returns (bool)
{
return self.flags[value];
}
}
contract C {
using Set for Set.Data; // este es el cambio crucial
Set.Data knownValues;
function register(uint value) {
// Aquí, todas las variables del tipo Set.Data tienen
// funciones miembro correspondientes.
// La siguiente llamada a la función es idéntica a
// Set.insert(knownValues, value)
require(knownValues.insert(value));
}
}
También es posible ampliar los tipos elementales de esta manera:
pragma solidity ^0.4.0;
library Search {
function indexOf(uint[] storage self, uint value) returns (uint) {
for (uint i = 0; i < self.length; i++)
if (self[i] == value) return i;
return uint(-1);
}
}
contract C {
using Search for uint[];
uint[] data;
function append(uint value) {
data.push(value);
}
function replace(uint _old, uint _new) {
// This performs the library function call
uint index = data.indexOf(_old);
if (index == uint(-1))
data.push(_new);
else
data[index] = _new;
}
}
Tenga en cuenta que todas las llamadas de biblioteca son llamadas de función EVM reales.
Esto significa que si pasa tipos de memoria o value, se realizará una copia, incluso de la variable
self
.
La única situación en la que no se realizará ninguna copia es cuando se usan variables de referencia de almacenamiento.
Solidity Assembly¶
Solidity defines an assembly language that can also be used without Solidity. This assembly language can also be used as “inline assembly” inside Solidity source code. We start with describing how to use inline assembly and how it differs from standalone assembly and then specify assembly itself.
Note
TODO: Write about how scoping rules of inline assembly are a bit different and the complications that arise when for example using internal functions of libraries. Furthermore, write about the symbols defined by the compiler.
Inline Assembly¶
For more fine-grained control especially in order to enhance the language by writing libraries, it is possible to interleave Solidity statements with inline assembly in a language close to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is often hard to address the correct stack slot and provide arguments to opcodes at the correct point on the stack. Solidity’s inline assembly tries to facilitate that and other issues arising when writing manual assembly by the following features:
- functional-style opcodes:
mul(1, add(2, 3))
instead ofpush1 3 push1 2 add push1 1 mul
- assembly-local variables:
let x := add(2, 3) let y := mload(0x40) x := add(x, y)
- access to external variables:
function f(uint x) { assembly { x := sub(x, 1) } }
- labels:
let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))
- loops:
for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }
- switch statements:
switch x case 0 { y := mul(x, 2) } default { y := 0 }
- function calls:
function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }
We now want to describe the inline assembly language in detail.
Warning
Inline assembly is a way to access the Ethereum Virtual Machine at a low level. This discards several important safety features of Solidity.
Example¶
The following example provides library code to access the code of another contract and
load it into a bytes
variable. This is not possible at all with “plain Solidity” and the
idea is that assembly libraries will be used to enhance the language in such ways.
pragma solidity ^0.4.0;
library GetCode {
function at(address _addr) returns (bytes o_code) {
assembly {
// retrieve the size of the code, this needs assembly
let size := extcodesize(_addr)
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
o_code := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(o_code, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(o_code, 0x20), 0, size)
}
}
}
Inline assembly could also be beneficial in cases where the optimizer fails to produce efficient code. Please be aware that assembly is much more difficult to write because the compiler does not perform checks, so you should use it for complex things only if you really know what you are doing.
pragma solidity ^0.4.12;
library VectorSum {
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] _data) returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i)
o_sum += _data[i];
}
// We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the
// array length.
function sumAsm(uint[] _data) returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
// Same as above, but accomplish the entire code within inline assembly.
function sumPureAsm(uint[] _data) returns (uint o_sum) {
assembly {
// Load the length (first 32 bytes)
let len := mload(_data)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// Iterate until the bound is not met.
for
{ let end := add(data, len) }
lt(data, end)
{ data := add(data, 0x20) }
{
o_sum := add(o_sum, mload(data))
}
}
}
}
Syntax¶
Assembly parses comments, literals and identifiers exactly as Solidity, so you can use the
usual //
and /* */
comments. Inline assembly is marked by assembly { ... }
and inside
these curly braces, the following can be used (see the later sections for more details)
- literals, i.e.
0x123
,42
or"abc"
(strings up to 32 characters)- opcodes (in “instruction style”), e.g.
mload sload dup1 sstore
, for a list see below- opcodes in functional style, e.g.
add(1, mlod(0))
- labels, e.g.
name:
- variable declarations, e.g.
let x := 7
,let x := add(y, 3)
orlet x
(initial value of empty (0) is assigned)- identifiers (labels or assembly-local variables and externals if used as inline assembly), e.g.
jump(name)
,3 x add
- assignments (in “instruction style”), e.g.
3 =: x
- assignments in functional style, e.g.
x := add(y, 3)
- blocks where local variables are scoped inside, e.g.
{ let x := 3 { let y := add(x, 1) } }
Opcodes¶
This document does not want to be a full description of the Ethereum virtual machine, but the following list can be used as a reference of its opcodes.
If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
Note that the order of arguments can be seen to be reversed in non-functional style (explained below).
Opcodes marked with -
do not push an item onto the stack, those marked with *
are
special and all others push exactly one item onto the stack.
In the following, mem[a...b)
signifies the bytes of memory starting at position a
up to
(excluding) position b
and storage[p]
signifies the storage contents at position p
.
The opcodes pushi
and jumpdest
cannot be used directly.
In the grammar, opcodes are represented as pre-defined identifiers.
stop | - | stop execution, identical to return(0,0) |
add(x, y) | x + y | |
sub(x, y) | x - y | |
mul(x, y) | x * y | |
div(x, y) | x / y | |
sdiv(x, y) | x / y, for signed numbers in two’s complement | |
mod(x, y) | x % y | |
smod(x, y) | x % y, for signed numbers in two’s complement | |
exp(x, y) | x to the power of y | |
not(x) | ~x, every bit of x is negated | |
lt(x, y) | 1 if x < y, 0 otherwise | |
gt(x, y) | 1 if x > y, 0 otherwise | |
slt(x, y) | 1 if x < y, 0 otherwise, for signed numbers in two’s complement | |
sgt(x, y) | 1 if x > y, 0 otherwise, for signed numbers in two’s complement | |
eq(x, y) | 1 if x == y, 0 otherwise | |
iszero(x) | 1 if x == 0, 0 otherwise | |
and(x, y) | bitwise and of x and y | |
or(x, y) | bitwise or of x and y | |
xor(x, y) | bitwise xor of x and y | |
byte(n, x) | nth byte of x, where the most significant byte is the 0th byte | |
addmod(x, y, m) | (x + y) % m with arbitrary precision arithmetics | |
mulmod(x, y, m) | (x * y) % m with arbitrary precision arithmetics | |
signextend(i, x) | sign extend from (i*8+7)th bit counting from least significant | |
keccak256(p, n) | keccak(mem[p...(p+n))) | |
sha3(p, n) | keccak(mem[p...(p+n))) | |
jump(label) | - | jump to label / code position |
jumpi(label, cond) | - | jump to label if cond is nonzero |
pc | current position in code | |
pop(x) | - | remove the element pushed by x |
dup1 ... dup16 | copy ith stack slot to the top (counting from top) | |
swap1 ... swap16 | * | swap topmost and ith stack slot below it |
mload(p) | mem[p..(p+32)) | |
mstore(p, v) | - | mem[p..(p+32)) := v |
mstore8(p, v) | - | mem[p] := v & 0xff - only modifies a single byte |
sload(p) | storage[p] | |
sstore(p, v) | - | storage[p] := v |
msize | size of memory, i.e. largest accessed memory index | |
gas | gas still available to execution | |
address | address of the current contract / execution context | |
balance(a) | wei balance at address a | |
caller | call sender (excluding delegatecall) | |
callvalue | wei sent together with the current call | |
calldataload(p) | call data starting from position p (32 bytes) | |
calldatasize | size of call data in bytes | |
calldatacopy(t, f, s) | - | copy s bytes from calldata at position f to mem at position t |
codesize | size of the code of the current contract / execution context | |
codecopy(t, f, s) | - | copy s bytes from code at position f to mem at position t |
extcodesize(a) | size of the code at address a | |
extcodecopy(a, t, f, s) | - | like codecopy(t, f, s) but take code at address a |
returndatasize | size of the last returndata | |
returndatacopy(t, f, s) | - | copy s bytes from returndata at position f to mem at position t |
create(v, p, s) | create new contract with code mem[p..(p+s)) and send v wei and return the new address | |
create2(v, n, p, s) | create new contract with code mem[p..(p+s)) at address keccak256(<address> . n . keccak256(mem[p..(p+s))) and send v wei and return the new address | |
call(g, a, v, in, insize, out, outsize) | call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success | |
callcode(g, a, v, in, insize, out, outsize) | identical to call but only use the code from a and stay in the context of the current contract otherwise | |
delegatecall(g, a, in, insize, out, outsize) | identical to callcode but also keep caller
and callvalue |
|
staticcall(g, a, in, insize, out, outsize) | identical to call(g, a, 0, in, insize, out, outsize) but do not allow state modifications | |
return(p, s) | - | end execution, return data mem[p..(p+s)) |
revert(p, s) | - | end execution, revert state changes, return data mem[p..(p+s)) |
selfdestruct(a) | - | end execution, destroy current contract and send funds to a |
invalid | - | end execution with invalid instruction |
log0(p, s) | - | log without topics and data mem[p..(p+s)) |
log1(p, s, t1) | - | log with topic t1 and data mem[p..(p+s)) |
log2(p, s, t1, t2) | - | log with topics t1, t2 and data mem[p..(p+s)) |
log3(p, s, t1, t2, t3) | - | log with topics t1, t2, t3 and data mem[p..(p+s)) |
log4(p, s, t1, t2, t3, t4) | - | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
origin | transaction sender | |
gasprice | gas price of the transaction | |
blockhash(b) | hash of block nr b - only for last 256 blocks excluding current | |
coinbase | current mining beneficiary | |
timestamp | timestamp of the current block in seconds since the epoch | |
number | current block number | |
difficulty | difficulty of the current block | |
gaslimit | block gas limit of the current block |
Literals¶
You can use integer constants by typing them in decimal or hexadecimal notation and an
appropriate PUSHi
instruction will automatically be generated. The following creates code
to add 2 and 3 resulting in 5 and then computes the bitwise and with the string “abc”.
Strings are stored left-aligned and cannot be longer than 32 bytes.
assembly { 2 3 add "abc" and }
Functional Style¶
You can type opcode after opcode in the same way they will end up in bytecode. For example
adding 3
to the contents in memory at position 0x80
would be
3 0x80 mload add 0x80 mstore
As it is often hard to see what the actual arguments for certain opcodes are, Solidity inline assembly also provides a “functional style” notation where the same code would be written as follows
mstore(0x80, add(mload(0x80), 3))
Functional style expressions cannot use instructional style internally, i.e.
1 2 mstore(0x80, add)
is not valid assembly, it has to be written as
mstore(0x80, add(2, 1))
. For opcodes that do not take arguments, the
parentheses can be omitted.
Note that the order of arguments is reversed in functional-style as opposed to the instruction-style way. If you use functional-style, the first argument will end up on the stack top.
Access to External Variables and Functions¶
Solidity variables and other identifiers can be accessed by simply using their name.
For memory variables, this will push the address and not the value onto the
stack. Storage variables are different: Values in storage might not occupy a
full storage slot, so their “address” is composed of a slot and a byte-offset
inside that slot. To retrieve the slot pointed to by the variable x
, you
used x_slot
and to retrieve the byte-offset you used x_offset
.
In assignments (see below), we can even use local Solidity variables to assign to.
Functions external to inline assembly can also be accessed: The assembly will push their entry label (with virtual function resolution applied). The calling semantics in solidity are:
- the caller pushes return label, arg1, arg2, ..., argn
- the call returns with ret1, ret2, ..., retm
This feature is still a bit cumbersome to use, because the stack offset essentially changes during the call, and thus references to local variables will be wrong.
pragma solidity ^0.4.11;
contract C {
uint b;
function f(uint x) returns (uint r) {
assembly {
r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
}
}
}
Labels¶
Another problem in EVM assembly is that jump
and jumpi
use absolute addresses
which can change easily. Solidity inline assembly provides labels to make the use of
jumps easier. Note that labels are a low-level feature and it is possible to write
efficient assembly without labels, just using assembly functions, loops and switch instructions
(see below). The following code computes an element in the Fibonacci series.
{
let n := calldataload(4)
let a := 1
let b := a
loop:
jumpi(loopend, eq(n, 0))
a add swap1
n := sub(n, 1)
jump(loop)
loopend:
mstore(0, a)
return(0, 0x20)
}
Please note that automatically accessing stack variables can only work if the assembler knows the current stack height. This fails to work if the jump source and target have different stack heights. It is still fine to use such jumps, but you should just not access any stack variables (even assembly variables) in that case.
Furthermore, the stack height analyser goes through the code opcode by opcode
(and not according to control flow), so in the following case, the assembler
will have a wrong impression about the stack height at label two
:
{
let x := 8
jump(two)
one:
// Here the stack height is 2 (because we pushed x and 7),
// but the assembler thinks it is 1 because it reads
// from top to bottom.
// Accessing the stack variable x here will lead to errors.
x := 9
jump(three)
two:
7 // push something onto the stack
jump(one)
three:
}
This problem can be fixed by manually adjusting the stack height for the assembler - you can provide a stack height delta that is added to the stack height just prior to the label. Note that you will not have to care about these things if you just use loops and assembly-level functions.
As an example how this can be done in extreme cases, please see the following.
{
let x := 8
jump(two)
0 // This code is unreachable but will adjust the stack height correctly
one:
x := 9 // Now x can be accessed properly.
jump(three)
pop // Similar negative correction.
two:
7 // push something onto the stack
jump(one)
three:
pop // We have to pop the manually pushed value here again.
}
Declaring Assembly-Local Variables¶
You can use the let
keyword to declare variables that are only visible in
inline assembly and actually only in the current {...}
-block. What happens
is that the let
instruction will create a new stack slot that is reserved
for the variable and automatically removed again when the end of the block
is reached. You need to provide an initial value for the variable which can
be just 0
, but it can also be a complex functional-style expression.
pragma solidity ^0.4.0;
contract C {
function f(uint x) returns (uint b) {
assembly {
let v := add(x, 1)
mstore(0x80, v)
{
let y := add(sload(v), 1)
b := y
} // y is "deallocated" here
b := add(b, v)
} // v is "deallocated" here
}
}
Assignments¶
Assignments are possible to assembly-local variables and to function-local variables. Take care that when you assign to variables that point to memory or storage, you will only change the pointer and not the data.
There are two kinds of assignments: functional-style and instruction-style.
For functional-style assignments (variable := value
), you need to provide a value in a
functional-style expression that results in exactly one stack value
and for instruction-style (=: variable
), the value is just taken from the stack top.
For both ways, the colon points to the name of the variable. The assignment
is performed by replacing the variable’s value on the stack by the new value.
{
let v := 0 // functional-style assignment as part of variable declaration
let g := add(v, 2)
sload(10)
=: v // instruction style assignment, puts the result of sload(10) into v
}
Switch¶
You can use a switch statement as a very basic version of “if/else”.
It takes the value of an expression and compares it to several constants.
The branch corresponding to the matching constant is taken. Contrary to the
error-prone behaviour of some programming languages, control flow does
not continue from one case to the next. There can be a fallback or default
case called default
.
{
let x := 0
switch calldataload(4)
case 0 {
x := calldataload(0x24)
}
default {
x := calldataload(0x44)
}
sstore(0, div(x, 2))
}
The list of cases does not require curly braces, but the body of a case does require them.
Loops¶
Assembly supports a simple for-style loop. For-style loops have a header containing an initializing part, a condition and a post-iteration part. The condition has to be a functional-style expression, while the other two are blocks. If the initializing part declares any variables, the scope of these variables is extended into the body (including the condition and the post-iteration part).
The following example computes the sum of an area in memory.
{
let x := 0
for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } {
x := add(x, mload(i))
}
}
For loops can also be written so that they behave like while loops: Simply leave the initialization and post-iteration parts empty.
{
let x := 0
let i := 0
for { } lt(i, 0x100) { } { // while(i < 0x100)
x := add(x, mload(i))
i := add(i, 0x20)
}
}
Functions¶
Assembly allows the definition of low-level functions. These take their arguments (and a return PC) from the stack and also put the results onto the stack. Calling a function looks the same way as executing a functional-style opcode.
Functions can be defined anywhere and are visible in the block they are
declared in. Inside a function, you cannot access local variables
defined outside of that function. There is no explicit return
statement.
If you call a function that returns multiple values, you have to assign
them to a tuple using a, b := f(x)
or let a, b := f(x)
.
The following example implements the power function by square-and-multiply.
{
function power(base, exponent) -> result {
switch exponent
case 0 { result := 1 }
case 1 { result := base }
default {
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 { result := mul(base, result) }
}
}
}
Things to Avoid¶
Inline assembly might have a quite high-level look, but it actually is extremely low-level. Function calls, loops and switches are converted by simple rewriting rules and after that, the only thing the assembler does for you is re-arranging functional-style opcodes, managing jump labels, counting stack height for variable access and removing stack slots for assembly-local variables when the end of their block is reached. Especially for those two last cases, it is important to know that the assembler only counts stack height from top to bottom, not necessarily following control flow. Furthermore, operations like swap will only swap the contents of the stack but not the location of variables.
Conventions in Solidity¶
In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
e.g. uint24
. In order to make them more efficient, most arithmetic operations just
treat them as 256-bit numbers and the higher-order bits are only cleaned at the
point where it is necessary, i.e. just shortly before they are written to memory
or before comparisons are performed. This means that if you access such a variable
from within inline assembly, you might have to manually clean the higher order bits
first.
Solidity manages memory in a very simple way: There is a “free memory pointer”
at position 0x40
in memory. If you want to allocate memory, just use the memory
from that point on and update the pointer accordingly.
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
even true for byte[]
, but not for bytes
and string
). Multi-dimensional memory
arrays are pointers to memory arrays. The length of a dynamic array is stored at the
first slot of the array and then only the array elements follow.
Warning
Statically-sized memory arrays do not have a length field, but it will be added soon to allow better convertibility between statically- and dynamically-sized arrays, so please do not rely on that.
Standalone Assembly¶
The assembly language described as inline assembly above can also be used standalone and in fact, the plan is to use it as an intermediate language for the Solidity compiler. In this form, it tries to achieve several goals:
- Programs written in it should be readable, even if the code is generated by a compiler from Solidity.
- The translation from assembly to bytecode should contain as few “surprises” as possible.
- Control flow should be easy to detect to help in formal verification and optimization.
In order to achieve the first and last goal, assembly provides high-level constructs
like for
loops, switch
statements and function calls. It should be possible
to write assembly programs that do not make use of explicit SWAP
, DUP
,
JUMP
and JUMPI
statements, because the first two obfuscate the data flow
and the last two obfuscate control flow. Furthermore, functional statements of
the form mul(add(x, y), 7)
are preferred over pure opcode statements like
7 y x add mul
because in the first form, it is much easier to see which
operand is used for which opcode.
The second goal is achieved by introducing a desugaring phase that only removes the higher level constructs in a very regular way and still allows inspecting the generated low-level assembly code. The only non-local operation performed by the assembler is name lookup of user-defined identifiers (functions, variables, ...), which follow very simple and regular scoping rules and cleanup of local variables from the stack.
Scoping: An identifier that is declared (label, variable, function, assembly) is only visible in the block where it was declared (including nested blocks inside the current block). It is not legal to access local variables across function borders, even if they would be in scope. Shadowing is not allowed. Local variables cannot be accessed before they were declared, but labels, functions and assemblies can. Assemblies are special blocks that are used for e.g. returning runtime code or creating contracts. No identifier from an outer assembly is visible in a sub-assembly.
If control flow passes over the end of a block, pop instructions are inserted that match the number of local variables declared in that block. Whenever a local variable is referenced, the code generator needs to know its current relative position in the stack and thus it needs to keep track of the current so-called stack height. Since all local variables are removed at the end of a block, the stack height before and after the block should be the same. If this is not the case, a warning is issued.
Why do we use higher-level constructs like switch
, for
and functions:
Using switch
, for
and functions, it should be possible to write
complex code without using jump
or jumpi
manually. This makes it much
easier to analyze the control flow, which allows for improved formal
verification and optimization.
Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. The position of all local variables on the stack needs to be known, otherwise neither references to local variables nor removing local variables automatically from the stack at the end of a block will work properly. The desugaring mechanism correctly inserts operations at unreachable blocks that adjust the stack height properly in case of jumps that do not have a continuing control flow.
Example:
We will follow an example compilation from Solidity to desugared assembly. We consider the runtime bytecode of the following Solidity program:
pragma solidity ^0.4.0;
contract C {
function f(uint x) returns (uint y) {
y = 1;
for (uint i = 0; i < x; i++)
y = 2 * y;
}
}
The following assembly will be generated:
{
mstore(0x40, 0x60) // store the "free memory pointer"
// function dispatcher
switch div(calldataload(0), exp(2, 226))
case 0xb3de648b {
let (r) = f(calldataload(4))
let ret := $allocate(0x20)
mstore(ret, r)
return(ret, 0x20)
}
default { revert(0, 0) }
// memory allocator
function $allocate(size) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, size))
}
// the contract function
function f(x) -> y {
y := 1
for { let i := 0 } lt(i, x) { i := add(i, 1) } {
y := mul(2, y)
}
}
}
After the desugaring phase it looks as follows:
{
mstore(0x40, 0x60)
{
let $0 := div(calldataload(0), exp(2, 226))
jumpi($case1, eq($0, 0xb3de648b))
jump($caseDefault)
$case1:
{
// the function call - we put return label and arguments on the stack
$ret1 calldataload(4) jump(f)
// This is unreachable code. Opcodes are added that mirror the
// effect of the function on the stack height: Arguments are
// removed and return values are introduced.
pop pop
let r := 0
$ret1: // the actual return point
$ret2 0x20 jump($allocate)
pop pop let ret := 0
$ret2:
mstore(ret, r)
return(ret, 0x20)
// although it is useless, the jump is automatically inserted,
// since the desugaring process is a purely syntactic operation that
// does not analyze control-flow
jump($endswitch)
}
$caseDefault:
{
revert(0, 0)
jump($endswitch)
}
$endswitch:
}
jump($afterFunction)
allocate:
{
// we jump over the unreachable code that introduces the function arguments
jump($start)
let $retpos := 0 let size := 0
$start:
// output variables live in the same scope as the arguments and is
// actually allocated.
let pos := 0
{
pos := mload(0x40)
mstore(0x40, add(pos, size))
}
// This code replaces the arguments by the return values and jumps back.
swap1 pop swap1 jump
// Again unreachable code that corrects stack height.
0 0
}
f:
{
jump($start)
let $retpos := 0 let x := 0
$start:
let y := 0
{
let i := 0
$for_begin:
jumpi($for_end, iszero(lt(i, x)))
{
y := mul(2, y)
}
$for_continue:
{ i := add(i, 1) }
jump($for_begin)
$for_end:
} // Here, a pop instruction will be inserted for i
swap1 pop swap1 jump
0 0
}
$afterFunction:
stop
}
Assembly happens in four stages:
- Parsing
- Desugaring (removes switch, for and functions)
- Opcode stream generation
- Bytecode generation
We will specify steps one to three in a pseudo-formal way. More formal specifications will follow.
Parsing / Grammar¶
The tasks of the parser are the following:
- Turn the byte stream into a token stream, discarding C++-style comments (a special comment exists for source references, but we will not explain it here).
- Turn the token stream into an AST according to the grammar below
- Register identifiers with the block they are defined in (annotation to the AST node) and note from which point on, variables can be accessed.
The assembly lexer follows the one defined by Solidity itself.
Whitespace is used to delimit tokens and it consists of the characters Space, Tab and Linefeed. Comments are regular JavaScript/C++ comments and are interpreted in the same way as Whitespace.
Grammar:
AssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem =
Identifier |
AssemblyBlock |
FunctionalAssemblyExpression |
AssemblyLocalDefinition |
FunctionalAssemblyAssignment |
AssemblyAssignment |
LabelDefinition |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
'break' | 'continue' |
SubAssembly | 'dataSize' '(' Identifier ')' |
LinkerSymbol |
'errorLabel' | 'bytecodeSize' |
NumberLiteral | StringLiteral | HexLiteral
Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
FunctionalAssemblyExpression = Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')'
AssemblyLocalDefinition = 'let' IdentifierOrList ':=' FunctionalAssemblyExpression
FunctionalAssemblyAssignment = IdentifierOrList ':=' FunctionalAssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)
FunctionalAssemblyExpression ( AssemblyBlock | FunctionalAssemblyExpression) AssemblyBlock
SubAssembly = 'assembly' Identifier AssemblyBlock
LinkerSymbol = 'linkerSymbol' '(' StringLiteral ')'
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
Desugaring¶
An AST transformation removes for, switch and function constructs. The result is still parseable by the same parser, but it will not use certain constructs. If jumpdests are added that are only jumped to and not continued at, information about the stack content is added, unless no local variables of outer scopes are accessed or the stack height is the same as for the previous instruction.
Pseudocode:
desugar item: AST -> AST =
match item {
AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
<name>:
{
jump($<name>_start)
let $retPC := 0 let argn := 0 ... let arg1 := 0
$<name>_start:
let ret1 := 0 ... let retm := 0
{ desugar(body) }
swap and pop items so that only ret1, ... retm, $retPC are left on the stack
jump
0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
}
AssemblyFor('for' { init } condition post body) ->
{
init // cannot be its own block because we want variable scope to extend into the body
// find I such that there are no labels $forI_*
$forI_begin:
jumpi($forI_end, iszero(condition))
{ body }
$forI_continue:
{ post }
jump($forI_begin)
$forI_end:
}
'break' ->
{
// find nearest enclosing scope with label $forI_end
pop all local variables that are defined at the current point
but not at $forI_end
jump($forI_end)
0 (as many as variables were removed above)
}
'continue' ->
{
// find nearest enclosing scope with label $forI_continue
pop all local variables that are defined at the current point
but not at $forI_continue
jump($forI_continue)
0 (as many as variables were removed above)
}
AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
{
// find I such that there is no $switchI* label or variable
let $switchI_value := condition
for each of cases match {
case val: -> jumpi($switchI_caseJ, eq($switchI_value, val))
}
if default block present: ->
{ defaultBlock jump($switchI_end) }
for each of cases match {
case val: { body } -> $switchI_caseJ: { body jump($switchI_end) }
}
$switchI_end:
}
FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) ->
{
if identifier is function <name> with n args and m ret values ->
{
// find I such that $funcallI_* does not exist
$funcallI_return argn ... arg2 arg1 jump(<name>)
pop (n + 1 times)
if the current context is `let (id1, ..., idm) := f(...)` ->
let id1 := 0 ... let idm := 0
$funcallI_return:
else ->
0 (m times)
$funcallI_return:
turn the functional expression that leads to the function call
into a statement stream
}
else -> desugar(children of node)
}
default node ->
desugar(children of node)
}
Opcode Stream Generation¶
During opcode stream generation, we keep track of the current stack height
in a counter,
so that accessing stack variables by name is possible. The stack height is modified with every opcode
that modifies the stack and with every label that is annotated with a stack
adjustment. Every time a new
local variable is introduced, it is registered together with the current
stack height. If a variable is accessed (either for copying its value or for
assignment), the appropriate DUP
or SWAP
instruction is selected depending
on the difference between the current stack height and the
stack height at the point the variable was introduced.
Pseudocode:
codegen item: AST -> opcode_stream =
match item {
AssemblyBlock({ items }) ->
join(codegen(item) for item in items)
if last generated opcode has continuing control flow:
POP for all local variables registered at the block (including variables
introduced by labels)
warn if the stack height at this point is not the same as at the start of the block
Identifier(id) ->
lookup id in the syntactic stack of blocks
match type of id
Local Variable ->
DUPi where i = 1 + stack_height - stack_height_of_identifier(id)
Label ->
// reference to be resolved during bytecode generation
PUSH<bytecode position of label>
SubAssembly ->
PUSH<bytecode position of subassembly data>
FunctionalAssemblyExpression(id ( arguments ) ) ->
join(codegen(arg) for arg in arguments.reversed())
id (which has to be an opcode, might be a function name later)
AssemblyLocalDefinition(let (id1, ..., idn) := expr) ->
register identifiers id1, ..., idn as locals in current block at current stack height
codegen(expr) - assert that expr returns n items to the stack
FunctionalAssemblyAssignment((id1, ..., idn) := expr) ->
lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables
codegen(expr)
for j = n, ..., i:
SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj)
POP
AssemblyAssignment(=: id) ->
look up id in the syntactic stack of blocks, assert that it is a variable
SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
POP
LabelDefinition(name:) ->
JUMPDEST
NumberLiteral(num) ->
PUSH<num interpreted as decimal and right-aligned>
HexLiteral(lit) ->
PUSH32<lit interpreted as hex and left-aligned>
StringLiteral(lit) ->
PUSH32<lit utf-8 encoded and left-aligned>
SubAssembly(assembly <name> block) ->
append codegen(block) at the end of the code
dataSize(<name>) ->
assert that <name> is a subassembly ->
PUSH32<size of code generated from subassembly <name>>
linkerSymbol(<lit>) ->
PUSH32<zeros> and append position to linker table
}
Miscellaneous¶
Layout of State Variables in Storage¶
Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position 0
. Multiple items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:
- The first item in a storage slot is stored lower-order aligned.
- Elementary types use only that many bytes that are necessary to store them.
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
- Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules).
Warning
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
It is only beneficial to use reduced-size arguments if you are dealing with storage values because the compiler will pack multiple elements into one storage slot, and thus, combine multiple reads or writes into a single operation. When dealing with function arguments or memory values, there is no inherent benefit because the compiler does not pack these values.
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
storage variables and struct
members such that they can be packed tightly. For example,
declaring your storage variables in the order of uint128, uint128, uint256
instead of
uint128, uint256, uint128
, as the former will only take up two slots of storage whereas the
latter will take up three.
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash computation to find the starting position of the value or the array data. These starting positions are always full stack slots.
The mapping or the dynamic array itself
occupies an (unfilled) slot in storage at some position p
according to the above rule (or by
recursively applying this rule for mappings to mappings or arrays of arrays). For a dynamic array, this slot stores the number of elements in the array (byte arrays and strings are an exception here, see below). For a mapping, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution).
Array data is located at keccak256(p)
and the value corresponding to a mapping key
k
is located at keccak256(k . p)
where .
is concatenation. If the value is again a
non-elementary type, the positions are found by adding an offset of keccak256(k . p)
.
bytes
and string
store their data in the same slot where also the length is stored if they are short. In particular: If the data is at most 31
bytes long, it is stored in the higher-order bytes (left aligned) and the lowest-order byte stores length * 2
. If it is longer, the main slot stores length * 2 + 1
and the data is stored as usual in keccak256(slot)
.
So for the following contract snippet:
pragma solidity ^0.4.0;
contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
}
The position of data[4][9].b
is at keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
.
Layout in Memory¶
Solidity reserves three 256-bit slots:
- 0 - 64: scratch space for hashing methods
- 64 - 96: currently allocated memory size (aka. free memory pointer)
Scratch space can be used between statements (ie. within inline assembly).
Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).
Warning
There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifecycle, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one shouldn’t expect the free memory to be zeroed out.
Layout of Call Data¶
When a Solidity contract is deployed and when it is called from an account, the input data is assumed to be in the format in the ABI specification. The ABI specification requires arguments to be padded to multiples of 32 bytes. The internal function calls use a different convention.
Internals - Cleaning Up Variables¶
When a value is shorter than 256-bit, in some cases the remaining bits must be cleaned. The Solidity compiler is designed to clean such remaining bits before any operations that might be adversely affected by the potential garbage in the remaining bits. For example, before writing a value to the memory, the remaining bits need to be cleared because the memory contents can be used for computing hashes or sent as the data of a message call. Similarly, before storing a value in the storage, the remaining bits need to be cleaned because otherwise the garbled value can be observed.
On the other hand, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered true
by JUMPI
instruction, we do not clean
the boolean values before they are used as the condition for
JUMPI
.
In addition to the design principle above, the Solidity compiler cleans input data when it is loaded onto the stack.
Different types have different rules for cleaning up invalid values:
Type | Valid Values | Invalid Values Mean |
---|---|---|
enum of n members | 0 until n - 1 | exception |
bool | 0 or 1 | 1 |
signed integers | sign-extended word | currently silently wraps; in the future exceptions will be thrown |
unsigned integers | higher bits zeroed | currently silently wraps; in the future exceptions will be thrown |
Internals - The Optimizer¶
The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at JUMPs
and JUMPDESTs
. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like constant + constant = sum_of_constants
or X * 1 = X
. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x.
At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all JUMP
and JUMPI
instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown JUMP
. If a JUMPI
is found whose condition evaluates to a constant, it is transformed to an unconditional jump.
As the last step, the code in each block is completely re-generated. A dependency graph is created from the expressions on the stack at the end of the block and every operation that is not part of this graph is essentially dropped. Now code is generated that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed) and finally, generates all values that are required to be on the stack in the correct place.
These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a JUMPI
and during the analysis, the condition evaluates to a constant, the JUMPI
is replaced depending on the value of the constant, and thus code like
var x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
is simplified to code which can also be compiled from
data[7] = 9;
return 1;
even though the instructions contained a jump in the beginning.
Source Mappings¶
As part of the AST output, the compiler provides the range of the source code that is represented by the respective node in the AST. This can be used for various purposes ranging from static analysis tools that report errors based on the AST and debugging tools that highlight local variables and their uses.
Furthermore, the compiler can also generate a mapping from the bytecode to the range in the source code that generated the instruction. This is again important for static analysis tools that operate on bytecode level and for displaying the current position in the source code inside a debugger or for breakpoint handling.
Both kinds of source mappings use integer indentifiers to refer to source files.
These are regular array indices into a list of source files usually called
"sourceList"
, which is part of the combined-json and the output of
the json / npm compiler.
The source mappings inside the AST use the following notation:
s:l:f
Where s
is the byte-offset to the start of the range in the source file,
l
is the length of the source range in bytes and f
is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of s:l:f:j
separated by ;
. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset (push instructions are longer than a single byte).
The fields s
, l
and f
are as above and j
can be either
i
, o
or -
signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
In order to compress these source mappings especially for bytecode, the following rules are used:
- If a field is empty, the value of the preceding element is used.
- If a
:
is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
1:2:1;1:9:1;2:1:2;2:1:2;2:1:2
1:2:1;:9;2::2;;
Contract Metadata¶
The Solidity compiler automatically generates a JSON file, the contract metadata, that contains information about the current contract. It can be used to query the compiler version, the sources used, the ABI and NatSpec documentation in order to more safely interact with the contract and to verify its source code.
The compiler appends a Swarm hash of the metadata file to the end of the bytecode (for details, see below) of each contract, so that you can retrieve the file in an authenticated way without having to resort to a centralized data provider.
Of course, you have to publish the metadata file to Swarm (or some other service)
so that others can access it. The file can be output by using solc --metadata
and the file will be called ContractName_meta.json
.
It will contain Swarm references to the source code, so you have to upload
all source files and the metadata file.
The metadata file has the following format. The example below is presented in a human-readable way. Properly formatted metadata should use quotes correctly, reduce whitespace to a minimum and sort the keys of all objects to arrive at a unique formatting. Comments are of course also not permitted and used here only for explanatory purposes.
{
// Required: The version of the metadata format
version: "1",
// Required: Source code language, basically selects a "sub-version"
// of the specification
language: "Solidity",
// Required: Details about the compiler, contents are specific
// to the language.
compiler: {
// Required for Solidity: Version of the compiler
version: "0.4.6+commit.2dabbdf0.Emscripten.clang",
// Optional: Hash of the compiler binary which produced this output
keccak256: "0x123..."
},
// Required: Compilation source files/source units, keys are file names
sources:
{
"myFile.sol": {
// Required: keccak256 hash of the source file
"keccak256": "0x123...",
// Required (unless "content" is used, see below): Sorted URL(s)
// to the source file, protocol is more or less arbitrary, but a
// Swarm URL is recommended
"urls": [ "bzzr://56ab..." ]
},
"mortal": {
// Required: keccak256 hash of the source file
"keccak256": "0x234...",
// Required (unless "url" is used): literal contents of the source file
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// Required: Compiler settings
settings:
{
// Required for Solidity: Sorted list of remappings
remappings: [ ":g/dir" ],
// Optional: Optimizer settings (enabled defaults to false)
optimizer: {
enabled: true,
runs: 500
},
// Required for Solidity: File and name of the contract or library this
// metadata is created for.
compilationTarget: {
"myFile.sol": "MyContract"
},
// Required for Solidity: Addresses for libraries used
libraries: {
"MyLib": "0x123123..."
}
},
// Required: Generated information about the contract.
output:
{
// Required: ABI definition of the contract
abi: [ ... ],
// Required: NatSpec user documentation of the contract
userdoc: [ ... ],
// Required: NatSpec developer documentation of the contract
devdoc: [ ... ],
}
}
Note
Note the ABI definition above has no fixed order. It can change with compiler versions.
Note
Since the bytecode of the resulting contract contains the metadata hash, any change to the metadata will result in a change of the bytecode. Furthermore, since the metadata includes a hash of all the sources used, a single whitespace change in any of the source codes will result in a different metadata, and subsequently a different bytecode.
Encoding of the Metadata Hash in the Bytecode¶
Because we might support other ways to retrieve the metadata file in the future,
the mapping {"bzzr0": <Swarm hash>}
is stored
CBOR-encoded. Since the beginning of that
encoding is not easy to find, its length is added in a two-byte big-endian
encoding. The current version of the Solidity compiler thus adds the following
to the end of the deployed bytecode:
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
So in order to retrieve the data, the end of the deployed bytecode can be checked to match that pattern and use the Swarm hash to retrieve the file.
Usage for Automatic Interface Generation and NatSpec¶
The metadata is used in the following way: A component that wants to interact with a contract (e.g. Mist) retrieves the code of the contract, from that the Swarm hash of a file which is then retrieved. That file is JSON-decoded into a structure like above.
The component can then use the ABI to automatically generate a rudimentary user interface for the contract.
Furthermore, Mist can use the userdoc to display a confirmation message to the user whenever they interact with the contract.
Usage for Source Code Verification¶
In order to verify the compilation, sources can be retrieved from Swarm
via the link in the metadata file.
The compiler of the correct version (which is checked to be part of the “official” compilers)
is invoked on that input with the specified settings. The resulting
bytecode is compared to the data of the creation transaction or CREATE
opcode data.
This automatically verifies the metadata since its hash is part of the bytecode.
Excess data corresponds to the constructor input data, which should be decoded
according to the interface and presented to the user.
Tips and Tricks¶
- Use
delete
on arrays to delete all its elements. - Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple
SSTORE
operations might be combined into a single (SSTORE
costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! - Make your state variables public - the compiler will create getters for you automatically.
- If you end up checking conditions on input or state a lot at the beginning of your functions, try using Function Modifiers.
- If your contract has a function called
send
but you want to use the built-in send-function, useaddress(contractVariable).send(amount)
. - Initialise storage structs with a single assignment:
x = MyStruct({a: 1, b: 2});
Cheatsheet¶
Order of Precedence of Operators¶
The following is the order of precedence for operators, listed in order of evaluation.
Precedence | Description | Operator |
---|---|---|
1 | Postfix increment and decrement | ++ , -- |
New expression | new <typename> |
|
Array subscripting | <array>[<index>] |
|
Member access | <object>.<member> |
|
Function-like call | <func>(<args...>) |
|
Parentheses | (<statement>) |
|
2 | Prefix increment and decrement | ++ , -- |
Unary plus and minus | + , - |
|
Unary operations | delete |
|
Logical NOT | ! |
|
Bitwise NOT | ~ |
|
3 | Exponentiation | ** |
4 | Multiplication, division and modulo | * , / , % |
5 | Addition and subtraction | + , - |
6 | Bitwise shift operators | << , >> |
7 | Bitwise AND | & |
8 | Bitwise XOR | ^ |
9 | Bitwise OR | | |
10 | Inequality operators | < , > , <= , >= |
11 | Equality operators | == , != |
12 | Logical AND | && |
13 | Logical OR | || |
14 | Ternary operator | <conditional> ? <if-true> : <if-false> |
15 | Assignment operators | = , |= , ^= , &= , <<= ,
>>= , += , -= , *= , /= ,
%= |
16 | Comma operator | , |
Global Variables¶
block.blockhash(uint blockNumber) returns (bytes32)
: hash of the given block - only works for 256 most recent blocksblock.coinbase
(address
): current block miner’s addressblock.difficulty
(uint
): current block difficultyblock.gaslimit
(uint
): current block gaslimitblock.number
(uint
): current block numberblock.timestamp
(uint
): current block timestampmsg.data
(bytes
): complete calldatamsg.gas
(uint
): remaining gasmsg.sender
(address
): sender of the message (current call)msg.value
(uint
): number of wei sent with the messagenow
(uint
): current block timestamp (alias forblock.timestamp
)tx.gasprice
(uint
): gas price of the transactiontx.origin
(address
): sender of the transaction (full call chain)assert(bool condition)
: abort execution and revert state changes if condition isfalse
(use for internal error)require(bool condition)
: abort execution and revert state changes if condition isfalse
(use for malformed input or error in external component)revert()
: abort execution and revert state changeskeccak256(...) returns (bytes32)
: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) argumentssha3(...) returns (bytes32)
: an alias tokeccak256
sha256(...) returns (bytes32)
: compute the SHA-256 hash of the (tightly packed) argumentsripemd160(...) returns (bytes20)
: compute the RIPEMD-160 hash of the (tightly packed) argumentsecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
: recover address associated with the public key from elliptic curve signature, return zero on erroraddmod(uint x, uint y, uint k) returns (uint)
: compute(x + y) % k
where the addition is performed with arbitrary precision and does not wrap around at2**256
mulmod(uint x, uint y, uint k) returns (uint)
: compute(x * y) % k
where the multiplication is performed with arbitrary precision and does not wrap around at2**256
this
(current contract’s type): the current contract, explicitly convertible toaddress
super
: the contract one level higher in the inheritance hierarchyselfdestruct(address recipient)
: destroy the current contract, sending its funds to the given addresssuicide(address recipieint)
: an alias toselfdestruct
<address>.balance
(uint256
): balance of the Address in Wei<address>.send(uint256 amount) returns (bool)
: send given amount of Wei to Address, returnsfalse
on failure<address>.transfer(uint256 amount)
: send given amount of Wei to Address, throws on failure
Function Visibility Specifiers¶
function myFunction() <visibility specifier> returns (bool) {
return true;
}
public
: visible externally and internally (creates a getter function for storage/state variables)private
: only visible in the current contractexternal
: only visible externally (only for functions) - i.e. can only be message-called (viathis.func
)internal
: only visible internally
Modifiers¶
pure
for functions: Disallows modification or access of state - this is not enforced yet.view
for functions: Disallows modification of state - this is not enforced yet.payable
for functions: Allows them to receive Ether together with a call.constant
for state variables: Disallows assignment (except initialisation), does not occupy storage slot.constant
for functions: Same asview
.anonymous
for events: Does not store event signature as topic.indexed
for event parameters: Stores the parameter as topic.
Reserved Keywords¶
These keywords are reserved in Solidity. They might become part of the syntax in the future:
abstract
, after
, case
, catch
, default
, final
, in
, inline
, let
, match
, null
,
of
, relocatable
, static
, switch
, try
, type
, typeof
.
Language Grammar¶
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')'
ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')'
TypeNameList = '(' ( TypeName (',' TypeName )* )? ')'
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | StateMutability )*
( 'returns' TypeNameList )?
StorageLocation = 'memory' | 'storage'
StateMutability = 'pure' | 'constant' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
| Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
| Expression ('<' | '>' | '<=' | '>=') Expression
| Expression ('==' | '!=') Expression
| Expression '&&' Expression
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
TupleExpression = '(' ( Expression ( ',' Expression )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | 'var'
| Int | Uint | Byte | Fixed | Ufixed
Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
Fixed = 'fixed' | ( 'fixed' DecimalNumber 'x' DecimalNumber )
Uixed = 'ufixed' | ( 'ufixed' DecimalNumber 'x' DecimalNumber )
InlineAssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
AssemblyLabel = Identifier ':'
FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'
Security Considerations¶
While it is usually quite easy to build software that works as expected, it is much harder to check that nobody can use it in a way that was not anticipated.
In Solidity, this is even more important because you can use smart contracts to handle tokens or, possibly, even more valuable things. Furthermore, every execution of a smart contract happens in public and, in addition to that, the source code is often available.
Of course you always have to consider how much is at stake: You can compare a smart contract with a web service that is open to the public (and thus, also to malicious actors) and perhaps even open source. If you only store your grocery list on that web service, you might not have to take too much care, but if you manage your bank account using that web service, you should be more careful.
This section will list some pitfalls and general security recommendations but can, of course, never be complete. Also, keep in mind that even if your smart contract code is bug-free, the compiler or the platform itself might have a bug. A list of some publicly known security-relevant bugs of the compiler can be found in the list of known bugs, which is also machine-readable. Note that there is a bug bounty program that covers the code generator of the Solidity compiler.
As always, with open source documentation, please help us extend this section (especially, some examples would not hurt)!
Pitfalls¶
Private Information and Randomness¶
Everything you use in a smart contract is publicly visible, even
local variables and state variables marked private
.
Using random numbers in smart contracts is quite tricky if you do not want miners to be able to cheat.
Re-Entrancy¶
Any interaction from a contract (A) with another contract (B) and any transfer of Ether hands over control to that contract (B). This makes it possible for B to call back into A before this interaction is completed. To give an example, the following code contains a bug (it is just a snippet and not a complete contract):
pragma solidity ^0.4.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() {
if (msg.sender.send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
The problem is not too serious here because of the limited gas as part
of send
, but it still exposes a weakness: Ether transfer always
includes code execution, so the recipient could be a contract that calls
back into withdraw
. This would let it get multiple refunds and
basically retrieve all the Ether in the contract.
To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as outlined further below:
pragma solidity ^0.4.11;
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() {
var share = shares[msg.sender];
shares[msg.sender] = 0;
msg.sender.transfer(share);
}
}
Note that re-entrancy is not only an effect of Ether transfer but of any function call on another contract. Furthermore, you also have to take multi-contract situations into account. A called contract could modify the state of another contract you depend on.
Gas Limit and Loops¶
Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully:
Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to
normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete
contract to be stalled at a certain point. This may not apply to constant
functions that are only executed
to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations
and stall those. Please be explicit about such cases in the documentation of your contracts.
Sending and Receiving Ether¶
- Neither contracts nor “external accounts” are currently able to prevent that someone sends them Ether.
Contracts can react on and reject a regular transfer, but there are ways
to move Ether without creating a message call. One way is to simply “mine to”
the contract address and the second way is using
selfdestruct(x)
. - If a contract receives Ether (without a function being called), the fallback function is executed. If it does not have a fallback function, the Ether will be rejected (by throwing an exception). During the execution of the fallback function, the contract can only rely on the “gas stipend” (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way. To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function (for example in the “details” section in Remix).
- There is a way to forward more gas to the receiving contract using
addr.call.value(x)()
. This is essentially the same asaddr.transfer(x)
, only that it forwards all remaining gas and opens up the ability for the recipient to perform more expensive actions (and it only returns a failure code and does not automatically propagate the error). This might include calling back into the sending contract or other state changes you might not have thought of. So it allows for great flexibility for honest users but also for malicious actors. - If you want to send Ether using
address.transfer
, there are certain details to be aware of:- If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract.
- Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call
depth, they can force the transfer to fail; take this possibility into account or use
send
and make sure to always check its return value. Better yet, write your contract using a pattern where the recipient can withdraw Ether instead. - Sending Ether can also fail because the execution of the recipient contract
requires more than the allotted amount of gas (explicitly by using
require
,assert
,revert
,throw
or because the operation is just too expensive) - it “runs out of gas” (OOG). If you usetransfer
orsend
with a return value check, this might provide a means for the recipient to block progress in the sending contract. Again, the best practice here is to use a “withdraw” pattern instead of a “send” pattern.
Callstack Depth¶
External function calls can fail any time because they exceed the maximum call stack of 1024. In such situations, Solidity throws an exception. Malicious actors might be able to force the call stack to a high value before they interact with your contract.
Note that .send()
does not throw an exception if the call stack is
depleted but rather returns false
in that case. The low-level functions
.call()
, .callcode()
and .delegatecall()
behave in the same way.
tx.origin¶
Never use tx.origin for authorization. Let’s say you have a wallet contract like this:
pragma solidity ^0.4.11;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
address owner;
function TxUserWallet() {
owner = msg.sender;
}
function transferTo(address dest, uint amount) {
require(tx.origin == owner);
dest.transfer(amount);
}
}
Now someone tricks you into sending ether to the address of this attack wallet:
pragma solidity ^0.4.11;
interface TxUserWallet {
function transferTo(address dest, uint amount);
}
contract TxAttackWallet {
address owner;
function TxAttackWallet() {
owner = msg.sender;
}
function() {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
If your wallet had checked msg.sender
for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking tx.origin
, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds.
Minor Details¶
- In
for (var i = 0; i < arrayName.length; i++) { ... }
, the type ofi
will beuint8
, because this is the smallest type that is required to hold the value0
. If the array has more than 255 elements, the loop will not terminate. - The
constant
keyword for functions is currently not enforced by the compiler. Furthermore, it is not enforced by the EVM, so a contract function that “claims” to be constant might still cause changes to the state. - Types that do not occupy the full 32 bytes might contain “dirty higher order bits”.
This is especially important if you access
msg.data
- it poses a malleability risk: You can craft transactions that call a functionf(uint8 x)
with a raw byte argument of0xff000001
and with0x00000001
. Both are fed to the contract and both will look like the number1
as far asx
is concerned, butmsg.data
will be different, so if you usekeccak256(msg.data)
for anything, you will get different results.
Recommendations¶
Restrict the Amount of Ether¶
Restrict the amount of Ether (or other tokens) that can be stored in a smart contract. If your source code, the compiler or the platform has a bug, these funds may be lost. If you want to limit your loss, limit the amount of Ether.
Keep it Small and Modular¶
Keep your contracts small and easily understandable. Single out unrelated functionality in other contracts or into libraries. General recommendations about source code quality of course apply: Limit the amount of local variables, the length of functions and so on. Document your functions so that others can see what your intention was and whether it is different than what the code does.
Use the Checks-Effects-Interactions Pattern¶
Most functions will first perform some checks (who called the function, are the arguments in range, did they send enough Ether, does the person have tokens, etc.). These checks should be done first.
As the second step, if all checks passed, effects to the state variables of the current contract should be made. Interaction with other contracts should be the very last step in any function.
Early contracts delayed some effects and waited for external function calls to return in a non-error state. This is often a serious mistake because of the re-entrancy problem explained above.
Note that, also, calls to known contracts might in turn cause calls to unknown contracts, so it is probably better to just always apply this pattern.
Include a Fail-Safe Mode¶
While making your system fully decentralised will remove any intermediary, it might be a good idea, especially for new code, to include some kind of fail-safe mechanism:
You can add a function in your smart contract that performs some self-checks like “Has any Ether leaked?”, “Is the sum of the tokens equal to the balance of the contract?” or similar things. Keep in mind that you cannot use too much gas for that, so help through off-chain computations might be needed there.
If the self-check fails, the contract automatically switches into some kind of “failsafe” mode, which, for example, disables most of the features, hands over control to a fixed and trusted third party or just converts the contract into a simple “give me back my money” contract.
Formal Verification¶
Using formal verification, it is possible to perform an automated mathematical proof that your source code fulfills a certain formal specification. The specification is still formal (just as the source code), but usually much simpler.
Note that formal verification itself can only help you understand the difference between what you did (the specification) and how you did it (the actual implementation). You still need to check whether the specification is what you wanted and that you did not miss any unintended effects of it.
Using the compiler¶
Using the Commandline Compiler¶
One of the build targets of the Solidity repository is solc
, the solidity commandline compiler.
Using solc --help
provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
If you only want to compile a single file, you run it as solc --bin sourceFile.sol
and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using solc --optimize --bin sourceFile.sol
. If you want to get some of the more advanced output variants of solc
, it is probably better to tell it to output everything to separate files using solc -o outputDirectory --bin --ast --asm sourceFile.sol
.
The commandline compiler will automatically read imported files from the filesystem, but
it is also possible to provide path redirects using prefix=path
in the following way:
solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
This essentially instructs the compiler to search for anything starting with
github.com/ethereum/dapp-bin/
under /usr/local/lib/dapp-bin
and if it does not
find the file there, it will look at /usr/local/lib/fallback
(the empty prefix
always matches). solc
will not read files from the filesystem that lie outside of
the remapping targets and outside of the directories where explicitly specified source
files reside, so things like import "/etc/passwd";
only work if you add =/
as a remapping.
If there are multiple matches due to remappings, the one with the longest common prefix is selected.
For security reasons the compiler has restrictions what directories it can access. Paths (and their subdirectories) of source files specified on the commandline and paths defined by remappings are allowed for import statements, but everything else is rejected. Additional paths (and their subdirectories) can be allowed via the --allow-paths /sample/path,/another/sample/path
switch.
If your contracts use libraries, you will notice that the bytecode contains substrings of the form __LibraryName______
. You can use solc
as a linker meaning that it will insert the library addresses for you at those points:
Either add --libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"
to your command to provide an address for each library or store the string in a file (one library per line) and run solc
using --libraries fileName
.
If solc
is called with the option --link
, all input files are interpreted to be unlinked binaries (hex-encoded) in the __LibraryName____
-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except --libraries
are ignored (including -o
) in this case.
If solc
is called with the option --standard-json
, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output.
Compiler Input and Output JSON Description¶
These JSON formats are used by the compiler API as well as are available through solc
. These are subject to change,
some fields are optional (as noted), but it is aimed at to only make backwards compatible changes.
The compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output.
Comments are of course not permitted and used here only for explanatory purposes.
Input Description¶
{
// Required: Source code language, such as "Solidity", "serpent", "lll", "assembly", etc.
language: "Solidity",
// Required
sources:
{
// The keys here are the "global" names of the source files,
// imports can use other files via remappings (see below).
"myFile.sol":
{
// Optional: keccak256 hash of the source file
// It is used to verify the retrieved content if imported via URLs.
"keccak256": "0x123...",
// Required (unless "content" is used, see below): URL(s) to the source file.
// URL(s) should be imported in this order and the result checked against the
// keccak256 hash (if available). If the hash doesn't match or none of the
// URL(s) result in success, an error should be raised.
"urls":
[
"bzzr://56ab...",
"ipfs://Qma...",
"file:///tmp/path/to/file.sol"
]
},
"mortal":
{
// Optional: keccak256 hash of the source file
"keccak256": "0x234...",
// Required (unless "urls" is used): literal contents of the source file
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// Optional
settings:
{
// Optional: Sorted list of remappings
remappings: [ ":g/dir" ],
// Optional: Optimizer settings (enabled defaults to false)
optimizer: {
enabled: true,
runs: 500
},
// Metadata settings (optional)
metadata: {
// Use only literal content and not URLs (false by default)
useLiteralContent: true
},
// Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different.
libraries: {
// The top level key is the the name of the source file where the library is used.
// If remappings are used, this source file should match the global path after remappings were applied.
// If this key is an empty string, that refers to a global level.
"myFile.sol": {
"MyLib": "0x123123..."
}
}
// The following can be used to select desired outputs.
// If this field is omitted, then the compiler loads and does type checking, but will not generate any outputs apart from errors.
// The first level key is the file name and the second is the contract name, where empty contract name refers to the file itself,
// while the star refers to all of the contracts.
//
// The available output types are as follows:
// abi - ABI
// ast - AST of all source files
// legacyAST - legacy AST of all source files
// devdoc - Developer documentation (natspec)
// userdoc - User documentation (natspec)
// metadata - Metadata
// ir - New assembly format before desugaring
// evm.assembly - New assembly format after desugaring
// evm.legacyAssembly - Old-style assembly format in JSON
// evm.bytecode.object - Bytecode object
// evm.bytecode.opcodes - Opcodes list
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
// evm.bytecode.linkReferences - Link references (if unlinked object)
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
// evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates
// ewasm.wast - eWASM S-expressions format (not supported atm)
// ewasm.wasm - eWASM binary format (not supported atm)
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
// target part of that output.
//
outputSelection: {
// Enable the metadata and bytecode outputs of every single contract.
"*": {
"*": [ "metadata", "evm.bytecode" ]
},
// Enable the abi and opcodes output of MyContract defined in file def.
"def": {
"MyContract": [ "abi", "evm.opcodes" ]
},
// Enable the source map output of every single contract.
"*": {
"*": [ "evm.sourceMap" ]
},
// Enable the legacy AST output of every single file.
"*": {
"": [ "legacyAST" ]
}
}
}
}
Output Description¶
{
// Optional: not present if no errors/warnings were encountered
errors: [
{
// Optional: Location within the source file.
sourceLocation: {
file: "sourceFile.sol",
start: 0,
end: 100
],
// Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc
type: "TypeError",
// Mandatory: Component where the error originated, such as "general", "ewasm", etc.
component: "general",
// Mandatory ("error" or "warning")
severity: "error",
// Mandatory
message: "Invalid keyword"
// Optional: the message formatted with source location
formattedMessage: "sourceFile.sol:100: Invalid keyword"
}
],
// This contains the file-level outputs. In can be limited/filtered by the outputSelection settings.
sources: {
"sourceFile.sol": {
// Identifier (used in source maps)
id: 1,
// The AST object
ast: {},
// The legacy AST object
legacyAST: {}
}
},
// This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
contracts: {
"sourceFile.sol": {
// If the language used has no contract names, this field should equal to an empty string.
"ContractName": {
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi: [],
// See the Metadata Output documentation (serialised JSON string)
metadata: "{...}",
// User documentation (natspec)
userdoc: {},
// Developer documentation (natspec)
devdoc: {},
// Intermediate representation (string)
ir: "",
// EVM-related outputs
evm: {
// Assembly (string)
assembly: "",
// Old-style assembly (object)
legacyAssembly: {},
// Bytecode and related details.
bytecode: {
// The bytecode as a hex string.
object: "00fe",
// Opcodes list (string)
opcodes: "",
// The source mapping as a string. See the source mapping definition.
sourceMap: "",
// If given, this is an unlinked object.
linkReferences: {
"libraryFile.sol": {
// Byte offsets into the bytecode. Linking replaces the 20 bytes located there.
"Library1": [
{ start: 0, length: 20 },
{ start: 200, length: 20 }
]
}
}
},
// The same layout as above.
deployedBytecode: { },
// The list of function hashes
methodIdentifiers: {
"delegate(address)": "5c19a95c"
},
// Function gas estimates
gasEstimates: {
creation: {
codeDepositCost: "420000",
executionCost: "infinite",
totalCost: "infinite"
},
external: {
"delegate(address)": "25000"
},
internal: {
"heavyLifting()": "infinite"
}
}
},
// eWASM related outputs
ewasm: {
// S-expressions format
wast: "",
// Binary format (hex string)
wasm: ""
}
}
}
}
}
Application Binary Interface Specification¶
Basic Design¶
The Application Binary Interface is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type, as described in this specification. The encoding is not self describing and thus requires a schema in order to decode.
We assume the interface functions of a contract are strongly typed, known at compilation time and static. No introspection mechanism will be provided. We assume that all contracts will have the interface definitions of any contracts they call available at compile-time.
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.
Function Selector¶
The first four bytes of the call data for a function call specifies the function to be called. It is the first (left, high-order in big-endian) four bytes of the Keccak (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype, i.e. the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used.
Argument Encoding¶
Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function.
Types¶
The following elementary types exist:
- uint<M>: unsigned integer type of M bits, 0 < M <= 256, M % 8 == 0. e.g. uint32, uint8, uint256.
- int<M>: two’s complement signed integer type of M bits, 0 < M <= 256, M % 8 == 0.
- address: equivalent to uint160, except for the assumed interpretation and language typing.
- uint, int: synonyms for uint256, int256 respectively (not to be used for computing the function selector).
- bool: equivalent to uint8 restricted to the values 0 and 1
- fixed<M>x<N>: signed fixed-point decimal number of M bits, 8 <= M <= 256, M % 8 ==0, and 0 < N <= 80, which denotes the value v as v / (10 ** N).
- ufixed<M>x<N>: unsigned variant of fixed<M>x<N>.
- fixed, ufixed: synonyms for fixed128x19, ufixed128x19 respectively (not to be used for computing the function selector).
- bytes<M>: binary type of M bytes, 0 < M <= 32.
- function: equivalent to bytes24: an address, followed by a function selector
The following (fixed-size) array type exists:
- <type>[M]: a fixed-length array of the given fixed-length type.
The following non-fixed-size types exist:
- bytes: dynamic sized byte sequence.
- string: dynamic sized unicode string assumed to be UTF-8 encoded.
- <type>[]: a variable-length array of the given fixed-length type.
Types can be combined to anonymous structs by enclosing a finite non-negative number of them inside parentheses, separated by commas:
- (T1,T2,...,Tn): anonymous struct (ordered tuple) consisting of the types T1, ..., Tn, n >= 0
It is possible to form structs of structs, arrays of structs and so on.
Formal Specification of the Encoding¶
We will now formally specify the encoding, such that it will have the following properties, which are especially useful if some arguments are nested arrays:
Properties:
- The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve a_i[k][l][r]. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
- The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative “addresses”
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
Definition: The following types are called “dynamic”: * bytes * string * T[] for any T * T[k] for any dynamic T and any k > 0 * (T1,...,Tk) if any Ti is dynamic for 1 <= i <= k
All other types are called “static”.
Definition: len(a) is the number of bytes in a binary string a. The type of len(a) is assumed to be uint256.
We define enc, the actual encoding, as a mapping of values of the ABI types to binary strings such that len(enc(X)) depends on the value of X if and only if the type of X is dynamic.
Definition: For any ABI value X, we recursively define enc(X), depending on the type of X being
(T1,...,Tk) for k >= 0 and any types T1, ..., Tk
enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))
where X(i) is the ith component of the value, and head and tail are defined for Ti being a static type as
head(X(i)) = enc(X(i)) and tail(X(i)) = “” (the empty string)
and as
head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1)))) tail(X(i)) = enc(X(i))
otherwise, i.e. if Ti is a dynamic type.
Note that in the dynamic case, head(X(i)) is well-defined since the lengths of the head parts only depend on the types and not the values. Its value is the offset of the beginning of tail(X(i)) relative to the start of enc(X).
T[k] for any T and k:
enc(X) = enc((X[0], ..., X[k-1]))
i.e. it is encoded as if it were an anonymous struct with k elements of the same type.
T[] where X has k elements (k is assumed to be of type uint256):
enc(X) = enc(k) enc([X[1], ..., X[k]])
i.e. it is encoded as if it were an array of static size k, prefixed with the number of elements.
bytes, of length k (which is assumed to be of type uint256):
- enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
uint256 followed by the actual value of X as a byte sequence, followed by the minimum number of zero-bytes such that len(enc(X)) is a multiple of 32.
string:
enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this value is interpreted as of bytes type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
uint<M>: enc(X) is the big-endian encoding of X, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes.
address: as in the uint160 case
int<M>: enc(X) is the big-endian two’s complement encoding of X, padded on the higher-oder (left) side with 0xff for negative X and with zero bytes for positive X such that the length is a multiple of 32 bytes.
bool: as in the uint8 case, where 1 is used for true and 0 for false
fixed<M>x<N>: enc(X) is enc(X * 10**N) where X * 10**N is interpreted as a int256.
fixed: as in the fixed128x19 case
ufixed<M>x<N>: enc(X) is enc(X * 10**N) where X * 10**N is interpreted as a uint256.
ufixed: as in the ufixed128x19 case
bytes<M>: enc(X) is the sequence of bytes in X padded with zero-bytes to a length of 32.
Note that for any X, len(enc(X)) is a multiple of 32.
Function Selector and Argument Encoding¶
All in all, a call to the function f with parameters a_1, ..., a_n is encoded as
function_selector(f) enc((a_1, ..., a_n))
and the return values v_1, ..., v_k of f are encoded as
enc((v_1, ..., v_k))
i.e. the values are combined into an anonymous struct and encoded.
Examples¶
Given the contract:
pragma solidity ^0.4.0;
contract Foo {
function bar(bytes3[2] xy) {}
function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }
function sam(bytes name, bool z, uint[] data) {}
}
Thus for our Foo example if we wanted to call baz with the parameters 69 and true, we would pass 68 bytes total, which can be broken down into:
- 0xcdcd77c0: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature baz(uint32,bool).
- 0x0000000000000000000000000000000000000000000000000000000000000045: the first parameter, a uint32 value 69 padded to 32 bytes
- 0x0000000000000000000000000000000000000000000000000000000000000001: the second parameter - boolean true, padded to 32 bytes
In total:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
It returns a single bool. If, for example, it were to return false, its output would be the single byte array 0x0000000000000000000000000000000000000000000000000000000000000000, a single bool.
If we wanted to call bar with the argument [“abc”, “def”], we would pass 68 bytes total, broken down into:
- 0xfce353f6: the Method ID. This is derived from the signature bar(bytes3[2]).
- 0x6162630000000000000000000000000000000000000000000000000000000000: the first part of the first parameter, a bytes3 value “abc” (left-aligned).
- 0x6465660000000000000000000000000000000000000000000000000000000000: the second part of the first parameter, a bytes3 value “def” (left-aligned).
In total:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
If we wanted to call sam with the arguments “dave”, true and [1,2,3], we would pass 292 bytes total, broken down into:
- 0xa5643bf2: the Method ID. This is derived from the signature sam(bytes,bool,uint256[]). Note that uint is replaced with its canonical representation uint256.
- 0x0000000000000000000000000000000000000000000000000000000000000060: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, 0x60.
- 0x0000000000000000000000000000000000000000000000000000000000000001: the second parameter: boolean true.
- 0x00000000000000000000000000000000000000000000000000000000000000a0: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, 0xa0.
- 0x0000000000000000000000000000000000000000000000000000000000000004: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.
- 0x6461766500000000000000000000000000000000000000000000000000000000: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of “dave”, padded on the right to 32 bytes.
- 0x0000000000000000000000000000000000000000000000000000000000000003: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.
- 0x0000000000000000000000000000000000000000000000000000000000000001: the first entry of the third parameter.
- 0x0000000000000000000000000000000000000000000000000000000000000002: the second entry of the third parameter.
- 0x0000000000000000000000000000000000000000000000000000000000000003: the third entry of the third parameter.
In total:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
Use of Dynamic Types¶
A call to a function with the signature f(uint,uint32[],bytes10,bytes) with values (0x123, [0x456, 0x789], “1234567890”, “Hello, world!”) is encoded in the following way:
We take the first four bytes of sha3(“f(uint256,uint32[],bytes10,bytes)”), i.e. 0x8be65246. Then we encode the head parts of all four arguments. For the static types uint256 and bytes10, these are directly the values we want to pass, whereas for the dynamic types uint32[] and bytes, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
- 0x0000000000000000000000000000000000000000000000000000000000000123 (0x123 padded to 32 bytes)
- 0x0000000000000000000000000000000000000000000000000000000000000080 (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
- 0x3132333435363738393000000000000000000000000000000000000000000000 (“1234567890” padded to 32 bytes on the right)
- 0x00000000000000000000000000000000000000000000000000000000000000e0 (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4*32 + 3*32 (see below))
After this, the data part of the first dynamic argument, [0x456, 0x789] follows:
- 0x0000000000000000000000000000000000000000000000000000000000000002 (number of elements of the array, 2)
- 0x0000000000000000000000000000000000000000000000000000000000000456 (first element)
- 0x0000000000000000000000000000000000000000000000000000000000000789 (second element)
Finally, we encode the data part of the second dynamic argument, “Hello, world!”:
- 0x000000000000000000000000000000000000000000000000000000000000000d (number of elements (bytes in this case): 13)
- 0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000 (“Hello, world!” padded to 32 bytes on the right)
All together, the encoding is (newline after function selector and each 32-bytes for clarity):
0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
Events¶
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract’s address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which as not indexed form the byte array of the event.
In effect, a log entry using this ABI is described as:
- address: the address of the contract (intrinsically provided by Ethereum);
- topics[0]: keccak(EVENT_NAME+”(“+EVENT_ARGS.map(canonical_type_of).join(”,”)+”)”) (canonical_type_of is a function that simply returns the canonical type of a given argument, e.g. for uint indexed foo, it would return uint256). If the event is declared as anonymous the topics[0] is not generated;
- topics[n]: EVENT_INDEXED_ARGS[n - 1] (EVENT_INDEXED_ARGS is the series of EVENT_ARGS that are indexed);
- data: abi_serialise(EVENT_NON_INDEXED_ARGS) (EVENT_NON_INDEXED_ARGS is the series of EVENT_ARGS that are not indexed, abi_serialise is the ABI serialisation function used for returning a series of typed values from a function, as described above).
JSON¶
The JSON format for a contract’s interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields:
- type: “function”, “constructor”, or “fallback” (the unnamed “default” function);
- name: the name of the function;
- inputs: an array of objects, each of which contains: * name: the name of the parameter; * type: the canonical type of the parameter.
- outputs: an array of objects similar to inputs, can be omitted if function doesn’t return anything;
- payable: true if function accepts ether, defaults to false;
- stateMutability: a string with one of the following values: pure (specified to not read blockchain state), view (specified to not modify the blockchain state), nonpayable and payable (same as payable above).
- constant: true if function is either pure or view
type can be omitted, defaulting to “function”.
Constructor and fallback function never have name or outputs. Fallback function doesn’t have inputs either.
Sending non-zero ether to non-payable function will throw. Don’t do it.
An event description is a JSON object with fairly similar fields:
- type: always “event”
- name: the name of the event;
- inputs: an array of objects, each of which contains: * name: the name of the parameter; * type: the canonical type of the parameter. * indexed: true if the field is part of the log’s topics, false if it one of the log’s data segment.
- anonymous: true if the event was declared as anonymous.
For example,
pragma solidity ^0.4.0;
contract Test {
function Test(){ b = 0x12345678901234567890123456789012; }
event Event(uint indexed a, bytes32 b)
event Event2(uint indexed a, bytes32 b)
function foo(uint a) { Event(a, b); }
bytes32 b;
}
would result in the JSON:
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
Style Guide¶
Introduction¶
This guide is intended to provide coding conventions for writing solidity code. This guide should be thought of as an evolving document that will change over time as useful conventions are found and old conventions are rendered obsolete.
Many projects will implement their own style guides. In the event of conflicts, project specific style guides take precedence.
The structure and many of the recommendations within this style guide were taken from python’s pep8 style guide.
The goal of this guide is not to be the right way or the best way to write solidity code. The goal of this guide is consistency. A quote from python’s pep8 captures this concept well.
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important. But most importantly: know when to be inconsistent – sometimes the style guide just doesn’t apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don’t hesitate to ask!
Code Layout¶
Indentation¶
Use 4 spaces per indentation level.
Tabs or Spaces¶
Spaces are the preferred indentation method.
Mixing tabs and spaces should be avoided.
Blank Lines¶
Surround top level declarations in solidity source with two blank lines.
Yes:
contract A {
...
}
contract B {
...
}
contract C {
...
}
No:
contract A {
...
}
contract B {
...
}
contract C {
...
}
Within a contract surround function declarations with a single blank line.
Blank lines may be omitted between groups of related one-liners (such as stub functions for an abstract contract)
Yes:
contract A {
function spam();
function ham();
}
contract B is A {
function spam() {
...
}
function ham() {
...
}
}
No:
contract A {
function spam() {
...
}
function ham() {
...
}
}
Source File Encoding¶
UTF-8 or ASCII encoding is preferred.
Imports¶
Import statements should always be placed at the top of the file.
Yes:
import "owned";
contract A {
...
}
contract B is owned {
...
}
No:
contract A {
...
}
import "owned";
contract B is owned {
...
}
Order of Functions¶
Ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier.
Functions should be grouped according to their visibility and ordered:
- constructor
- fallback function (if exists)
- external
- public
- internal
- private
Within a grouping, place the constant
functions last.
Yes:
contract A {
function A() {
...
}
function() {
...
}
// External functions
// ...
// External functions that are constant
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
No:
contract A {
// External functions
// ...
// Private functions
// ...
// Public functions
// ...
function A() {
...
}
function() {
...
}
// Internal functions
// ...
}
Whitespace in Expressions¶
Avoid extraneous whitespace in the following situations:
Immediately inside parenthesis, brackets or braces, with the exception of single-line function declarations.
Yes:
spam(ham[1], Coin({name: "ham"}));
No:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
Exception:
function singleLine() { spam(); }
Immediately before a comma, semicolon:
Yes:
function spam(uint i, Coin coin);
No:
function spam(uint i , Coin coin) ;
- More than one space around an assignment or other operator to align with
- another:
Yes:
x = 1;
y = 2;
long_variable = 3;
No:
x = 1;
y = 2;
long_variable = 3;
Don’t include a whitespace in the fallback function:
Yes:
function() {
...
}
No:
function () {
...
}
Control Structures¶
The braces denoting the body of a contract, library, functions and structs should:
- open on the same line as the declaration
- close on their own line at the same indentation level as the beginning of the declaration.
- The opening brace should be proceeded by a single space.
Yes:
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
No:
contract Coin
{
struct Bank {
address owner;
uint balance;
}
}
The same recommendations apply to the control structures if
, else
, while
,
and for
.
Additionally there should be a single space between the control structures
if
, while
, and for
and the parenthetic block representing the
conditional, as well as a single space between the conditional parenthetic
block and the opening brace.
Yes:
if (...) {
...
}
for (...) {
...
}
No:
if (...)
{
...
}
while(...){
}
for (...) {
...;}
For control structures whose body contains a single statement, omitting the braces is ok if the statement is contained on a single line.
Yes:
if (x < 10)
x += 1;
No:
if (x < 10)
someArray.push(Coin({
name: 'spam',
value: 42
}));
For if
blocks which have an else
or else if
clause, the else
should be
placed on the same line as the if
‘s closing brace. This is an exception compared
to the rules of other block-like structures.
Yes:
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
No:
if (x < 3) {
x += 1;
}
else {
x -= 1;
}
Function Declaration¶
For short function declarations, it is recommended for the opening brace of the function body to be kept on the same line as the function declaration.
The closing brace should be at the same indentation level as the function declaration.
The opening brace should be preceeded by a single space.
Yes:
function increment(uint x) returns (uint) {
return x + 1;
}
function increment(uint x) public onlyowner returns (uint) {
return x + 1;
}
No:
function increment(uint x) returns (uint)
{
return x + 1;
}
function increment(uint x) returns (uint){
return x + 1;
}
function increment(uint x) returns (uint) {
return x + 1;
}
function increment(uint x) returns (uint) {
return x + 1;}
The visibility modifiers for a function should come before any custom modifiers.
Yes:
function kill() public onlyowner {
selfdestruct(owner);
}
No:
function kill() onlyowner public {
selfdestruct(owner);
}
For long function declarations, it is recommended to drop each argument onto it’s own line at the same indentation level as the function body. The closing parenthesis and opening bracket should be placed on their own line as well at the same indentation level as the function declaration.
Yes:
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f
) {
doSomething();
}
No:
function thisFunctionHasLotsOfArguments(address a, address b, address c,
address d, address e, address f) {
doSomething();
}
function thisFunctionHasLotsOfArguments(address a,
address b,
address c,
address d,
address e,
address f) {
doSomething();
}
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f) {
doSomething();
}
If a long function declaration has modifiers, then each modifier should be dropped to it’s own line.
Yes:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(
address x,
address y,
address z,
)
public
onlyowner
priced
returns (address)
{
doSomething();
}
No:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public onlyowner priced returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
For constructor functions on inherited contracts whose bases require arguments, it is recommended to drop the base constructors onto new lines in the same manner as modifiers if the function declaration is long or hard to read.
Yes:
contract A is B, C, D {
function A(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
{
// do something with param5
}
}
No:
contract A is B, C, D {
function A(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
{
// do something with param5
}
}
contract A is B, C, D {
function A(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4) {
// do something with param5
}
}
When declaring short functions with a single statement, it is permissible to do it on a single line.
Permissible:
function shortFunction() { doSomething(); }
These guidelines for function declarations are intended to improve readability. Authors should use their best judgement as this guide does not try to cover all possible permutations for function declarations.
Mappings¶
TODO
Variable Declarations¶
Declarations of array variables should not have a space between the type and the brackets.
Yes:
uint[] x;
No:
uint [] x;
Other Recommendations¶
- Strings should be quoted with double-quotes instead of single-quotes.
Yes:
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
No:
str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
- Surround operators with a single space on either side.
Yes:
x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;
No:
x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
- Operators with a higher priority than others can exclude surrounding whitespace in order to denote precedence. This is meant to allow for improved readability for complex statement. You should always use the same amount of whitespace on either side of an operator:
Yes:
x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);
No:
x = 2** 3 + 5;
x = y+z;
x +=1;
Naming Conventions¶
Naming conventions are powerful when adopted and used broadly. The use of different conventions can convey significant meta information that would otherwise not be immediately available.
The naming recommendations given here are intended to improve the readability, and thus they are not rules, but rather guidelines to try and help convey the most information through the names of things.
Lastly, consistency within a codebase should always supercede any conventions outlined in this document.
Naming Styles¶
To avoid confusion, the following names will be used to refer to different naming styles.
b
(single lowercase letter)B
(single uppercase letter)lowercase
lower_case_with_underscores
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
CapitalizedWords
(or CapWords)mixedCase
(differs from CapitalizedWords by initial lowercase character!)Capitalized_Words_With_Underscores
Note
When using abbreviations in CapWords, capitalize all the letters of the abbreviation. Thus HTTPServerError is better than HttpServerError.
Names to Avoid¶
l
- Lowercase letter elO
- Uppercase letter ohI
- Uppercase letter eye
Never use any of these for single letter variable names. They are often indistinguishable from the numerals one and zero.
Contract and Library Names¶
Contracts and libraries should be named using the CapWords style.
Events¶
Events should be named using the CapWords style.
Function Names¶
Functions should use mixedCase.
Function Arguments¶
When writing library functions that operate on a custom struct, the struct
should be the first argument and should always be named self
.
Local and State Variables¶
Use mixedCase.
Constants¶
Constants should be named with all capital letters with underscores separating
words. (for example:MAX_BLOCKS
)
Modifiers¶
Use mixedCase.
Avoiding Collisions¶
single_trailing_underscore_
This convention is suggested when the desired name collides with that of a built-in or otherwise reserved name.
General Recommendations¶
TODO
Common Patterns¶
Withdrawal from Contracts¶
The recommended method of sending funds after an effect
is using the withdrawal pattern. Although the most intuitive
method of sending Ether, as a result of an effect, is a
direct send
call, this is not recommended as it
introduces a potential security risk. You may read
more about this on the Security Considerations page.
This is an example of the withdrawal pattern in practice in a contract where the goal is to send the most money to the contract in order to become the “richest”, inspired by King of the Ether.
In the following contract, if you are usurped as the richest, you will receive the funds of the person who has gone on to become the new richest.
pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
function withdraw() {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
This is as opposed to the more intuitive sending pattern:
pragma solidity ^0.4.11;
contract SendContract {
address public richest;
uint public mostSent;
function SendContract() payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
// This line can cause problems (explained below).
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
Notice that, in this example, an attacker could trap the
contract into an unusable state by causing richest
to be
the address of a contract that has a fallback function
which fails (e.g. by using revert()
or by just
conssuming more than the 2300 gas stipend). That way,
whenever transfer
is called to deliver funds to the
“poisoned” contract, it will fail and thus also becomeRichest
will fail, with the contract being stuck forever.
In contrast, if you use the “withdraw” pattern from the first example, the attacker can only cause his or her own withdraw to fail and not the rest of the contract’s workings.
Restricting Access¶
Restricting access is a common pattern for contracts. Note that you can never restrict any human or computer from reading the content of your transactions or your contract’s state. You can make it a bit harder by using encryption, but if your contract is supposed to read the data, so will everyone else.
You can restrict read access to your contract’s state
by other contracts. That is actually the default
unless you declare make your state variables public
.
Furthermore, you can restrict who can make modifications to your contract’s state or call your contract’s functions and this is what this page is about.
The use of function modifiers makes these restrictions highly readable.
pragma solidity ^0.4.11;
contract AccessRestriction {
// These will be assigned at the construction
// phase, where `msg.sender` is the account
// creating this contract.
address public owner = msg.sender;
uint public creationTime = now;
// Modifiers can be used to change
// the body of a function.
// If this modifier is used, it will
// prepend a check that only passes
// if the function is called from
// a certain address.
modifier onlyBy(address _account)
{
require(msg.sender == _account);
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
_;
}
/// Make `_newOwner` the new owner of this
/// contract.
function changeOwner(address _newOwner)
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(now >= _time);
_;
}
/// Erase ownership information.
/// May only be called 6 weeks after
/// the contract has been created.
function disown()
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// This modifier requires a certain
// fee being associated with a function call.
// If the caller sent too much, he or she is
// refunded, but only after the function body.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
require(msg.value >= _amount);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)
costs(200 ether)
{
owner = _newOwner;
// just some example condition
if (uint(owner) & 0 == 1)
// This did not refund for Solidity
// before version 0.4.0.
return;
// refund overpaid fees
}
}
A more specialised way in which access to function calls can be restricted will be discussed in the next example.
State Machine¶
Contracts often act as a state machine, which means that they have certain stages in which they behave differently or in which different functions can be called. A function call often ends a stage and transitions the contract into the next stage (especially if the contract models interaction). It is also common that some stages are automatically reached at a certain point in time.
An example for this is a blind auction contract which starts in the stage “accepting blinded bids”, then transitions to “revealing bids” which is ended by “determine auction outcome”.
Function modifiers can be used in this situation to model the states and guard against incorrect usage of the contract.
Example¶
In the following example,
the modifier atStage
ensures that the function can
only be called at a certain stage.
Automatic timed transitions
are handled by the modifier timeTransitions
, which
should be used for all functions.
Note
Modifier Order Matters. If atStage is combined with timedTransitions, make sure that you mention it after the latter, so that the new stage is taken into account.
Finally, the modifier transitionNext
can be used
to automatically go to the next stage when the
function finishes.
Note
Modifier May be Skipped. This only applies to Solidity before version 0.4.0: Since modifiers are applied by simply replacing code and not by using a function call, the code in the transitionNext modifier can be skipped if the function itself uses return. If you want to do that, make sure to call nextStage manually from those functions. Starting with version 0.4.0, modifier code will run even if the function explicitly returns.
pragma solidity ^0.4.11;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(stage == _stage);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// Perform timed transitions. Be sure to mention
// this modifier first, otherwise the guards
// will not take the new stage into account.
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
// The other stages transition by transaction
_;
}
// Order of the modifiers matters here!
function bid()
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// We will not implement that here
}
function reveal()
timedTransitions
atStage(Stages.RevealBids)
{
}
// This modifier goes to the next stage
// after the function is done.
modifier transitionNext()
{
_;
nextStage();
}
function g()
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}
function h()
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext
{
}
function i()
timedTransitions
atStage(Stages.Finished)
{
}
}
List of Known Bugs¶
Below, you can find a JSON-formatted list of some of the known security-relevant bugs in the Solidity compiler. The file itself is hosted in the Github repository. The list stretches back as far as version 0.3.0, bugs known to be present only in versions preceding that are not listed.
There is another file called bugs_by_version.json, which can be used to check which bugs affect a specific version of the compiler.
Contract source verification tools and also other tools interacting with contracts should consult this list according to the following criteria:
- It is mildly suspicious if a contract was compiled with a nightly compiler version instead of a released version. This list does not keep track of unreleased or nightly versions.
- It is also mildly suspicious if a contract was compiled with a version that was not the most recent at the time the contract was created. For contracts created from other contracts, you have to follow the creation chain back to a transaction and use the date of that transaction as creation date.
- It is highly suspicious if a contract was compiled with a compiler that contains a known bug and the contract was created at a time where a newer compiler version containing a fix was already released.
The JSON file of known bugs below is an array of objects, one for each bug, with the following keys:
- name
- Unique name given to the bug
- summary
- Short description of the bug
- description
- Detailed description of the bug
- link
- URL of a website with more detailed information, optional
- introduced
- The first published compiler version that contained the bug, optional
- fixed
- The first published compiler version that did not contain the bug anymore
- publish
- The date at which the bug became known publicly, optional
- severity
- Severity of the bug: low, medium, high. Takes into account discoverability in contract tests, likelihood of occurrence and potential damage by exploits.
- conditions
- Conditions that have to be met to trigger the bug. Currently, this
is an object that can contain a boolean value
optimizer
, which means that the optimizer has to be switched on to enable the bug. If no conditions are given, assume that the bug is present.
[
{
"name": "DelegateCallReturnValue",
"summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.",
"description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successuful.",
"introduced": "0.3.0",
"fixed": "0.4.15",
"severity": "low"
},
{
"name": "ECRecoverMalformedInput",
"summary": "The ecrecover() builtin can return garbage for malformed input.",
"description": "The ecrecover precompile does not properly signal failure for malformed input (especially in the 'v' argument) and thus the Solidity function can return data that was previously present in the return area in memory.",
"fixed": "0.4.14",
"severity": "medium"
},
{
"name": "SkipEmptyStringLiteral",
"summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.",
"description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.",
"fixed": "0.4.12",
"severity": "low"
},
{
"name": "ConstantOptimizerSubtraction",
"summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.",
"description": "The optimizer tries to represent any number in the bytecode by routines that compute them with less gas. For some special numbers, an incorrect routine is generated. This could allow an attacker to e.g. trick victims about a specific amount of ether, or function calls to call different functions (or none at all).",
"link": "https://blog.ethereum.org/2017/05/03/solidity-optimizer-bug/",
"fixed": "0.4.11",
"severity": "low",
"conditions": {
"optimizer": true
}
},
{
"name": "IdentityPrecompileReturnIgnored",
"summary": "Failure of the identity precompile was ignored.",
"description": "Calls to the identity contract, which is used for copying memory, ignored its return value. On the public chain, calls to the identity precompile can be made in a way that they never fail, but this might be different on private chains.",
"severity": "low",
"fixed": "0.4.7"
},
{
"name": "OptimizerStateKnowledgeNotResetForJumpdest",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was simplified to just use the empty state, but this implementation was not done properly. This bug can cause data corruption.",
"severity": "medium",
"introduced": "0.4.5",
"fixed": "0.4.6",
"conditions": {
"optimizer": true
}
},
{
"name": "HighOrderByteCleanStorage",
"summary": "For short types, the high order bytes were not cleaned properly and could overwrite existing data.",
"description": "Types shorter than 32 bytes are packed together into the same 32 byte storage slot, but storage writes always write 32 bytes. For some types, the higher order bytes were not cleaned properly, which made it sometimes possible to overwrite a variable in storage when writing to another one.",
"link": "https://blog.ethereum.org/2016/11/01/security-alert-solidity-variables-can-overwritten-storage/",
"severity": "high",
"introduced": "0.1.6",
"fixed": "0.4.4"
},
{
"name": "OptimizerStaleKnowledgeAboutSHA3",
"summary": "The optimizer did not properly reset its knowledge about SHA3 operations resulting in some hashes (also used for storage variable positions) not being calculated correctly.",
"description": "The optimizer performs symbolic execution in order to save re-evaluating expressions whose value is already known. This knowledge was not properly reset across control flow paths and thus the optimizer sometimes thought that the result of a SHA3 operation is already present on the stack. This could result in data corruption by accessing the wrong storage slot.",
"severity": "medium",
"fixed": "0.4.3",
"conditions": {
"optimizer": true
}
},
{
"name": "LibrariesNotCallableFromPayableFunctions",
"summary": "Library functions threw an exception when called from a call that received Ether.",
"description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.",
"severity": "low",
"introduced": "0.4.0",
"fixed": "0.4.2"
},
{
"name": "SendFailsForZeroEther",
"summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.",
"description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.",
"severity": "low",
"fixed": "0.4.0"
},
{
"name": "DynamicAllocationInfiniteLoop",
"summary": "Dynamic allocation of an empty memory array caused an infinite loop and thus an exception.",
"description": "Memory arrays can be created provided a length. If this length is zero, code was generated that did not terminate and thus consumed all gas.",
"severity": "low",
"fixed": "0.3.6"
},
{
"name": "OptimizerClearStateOnCodePathJoin",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was not done correctly. This bug can cause data corruption, but it is probably quite hard to use for targeted attacks.",
"severity": "low",
"fixed": "0.3.6",
"conditions": {
"optimizer": true
}
},
{
"name": "CleanBytesHigherOrderBits",
"summary": "The higher order bits of short bytesNN types were not cleaned before comparison.",
"description": "Two variables of type bytesNN were considered different if their higher order bits, which are not part of the actual value, were different. An attacker might use this to reach seemingly unreachable code paths by providing incorrectly formatted input data.",
"severity": "medium/high",
"fixed": "0.3.3"
},
{
"name": "ArrayAccessCleanHigherOrderBits",
"summary": "Access to array elements for arrays of types with less than 32 bytes did not correctly clean the higher order bits, causing corruption in other array elements.",
"description": "Multiple elements of an array of values that are shorter than 17 bytes are packed into the same storage slot. Writing to a single element of such an array did not properly clean the higher order bytes and thus could lead to data corruption.",
"severity": "medium/high",
"fixed": "0.3.1"
},
{
"name": "AncientCompiler",
"summary": "This compiler version is ancient and might contain several undocumented or undiscovered bugs.",
"description": "The list of bugs is only kept for compiler versions starting from 0.3.0, so older versions might contain undocumented bugs.",
"severity": "high",
"fixed": "0.3.0"
}
]
Contributing¶
Help is always appreciated!
To get started, you can try Building from Source in order to familiarize yourself with the components of Solidity and the build process. Also, it may be useful to become well-versed at writing smart-contracts in Solidity.
In particular, we need help in the following areas:
- Improving the documentation
- Responding to questions from other users on StackExchange and the Solidity Gitter
- Fixing and responding to Solidity’s GitHub issues, especially those tagged as up-for-grabs which are meant as introductory issues for external contributors.
How to Report Issues¶
To report an issue, please use the GitHub issues tracker. When reporting issues, please mention the following details:
- Which version of Solidity you are using
- What was the source code (if applicable)
- Which platform are you running on
- How to reproduce the issue
- What was the result of the issue
- What the expected behaviour is
Reducing the source code that caused the issue to a bare minimum is always very helpful and sometimes even clarifies a misunderstanding.
Workflow for Pull Requests¶
In order to contribute, please fork off of the develop
branch and make your
changes there. Your commit messages should detail why you made your change
in addition to what you did (unless it is a tiny change).
If you need to pull in any changes from develop
after making your fork (for
example, to resolve potential merge conflicts), please avoid using git merge
and instead, git rebase
your branch.
Additionally, if you are writing a new feature, please ensure you write appropriate
Boost test cases and place them under test/
.
However, if you are making a larger change, please consult with the Gitter channel, first.
Finally, please make sure you respect the coding standards for this project. Also, even though we do CI testing, please test your code and ensure that it builds locally before submitting a pull request.
Thank you for your help!
Running the compiler tests¶
Solidity includes different types of tests. They are included in the application
called soltest
. Some of them require the cpp-ethereum
client in testing mode.
To run cpp-ethereum
in testing mode: eth --test -d /tmp/testeth
.
To run the tests: soltest -- --ipcpath /tmp/testeth/geth.ipc
.
To run a subset of tests, filters can be used:
soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc
, where TestName
can be a wildcard *
.
Alternatively, there is a testing script at scripts/test.sh
which executes all tests.
Whiskers¶
Whiskers is a templating system similar to Mustache. It is used by the compiler in various places to aid readability, and thus maintainability and verifiability, of the code.
The syntax comes with a substantial difference to Mustache: the template markers {{
and }}
are
replaced by <
and >
in order to aid parsing and avoid conflicts with Inline Assembly
(The symbols <
and >
are invalid in inline assembly, while {
and }
are used to delimit blocks).
Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future.
A rough specification is the following:
Any occurrence of <name>
is replaced by the string-value of the supplied variable name
without any
escaping and without iterated replacements. An area can be delimited by <#name>...</name>
. It is replaced
by as many concatenations of its contents as there were sets of variables supplied to the template system,
each time replacing any <inner>
items by their respective value. Top-level variables can also be used
inside such areas.
Frequently Asked Questions¶
This list was originally compiled by fivedogit.
Basic Questions¶
Example contracts¶
There are some contract examples by fivedogit and there should be a test contract for every single feature of Solidity.
Is it possible to do something on a specific block number? (e.g. publish a contract or execute a transaction)¶
Transactions are not guaranteed to happen on the next block or any future specific block, since it is up to the miners to include transactions and not up to the submitter of the transaction. This applies to function calls/transactions and contract creation transactions.
If you want to schedule future calls of your contract, you can use the alarm clock.
What is the transaction “payload”?¶
This is just the bytecode “data” sent along with the request.
Is there a decompiler available?¶
There is no decompiler to Solidity. This is in principle possible to some degree, but for example variable names will be lost and great effort will be necessary to make it look similar to the original source code.
Bytecode can be decompiled to opcodes, a service that is provided by several blockchain explorers.
Contracts on the blockchain should have their original source code published if they are to be used by third parties.
Create a contract that can be killed and return funds¶
First, a word of warning: Killing contracts sounds like a good idea, because “cleaning up” is always good, but as seen above, it does not really clean up. Furthermore, if Ether is sent to removed contracts, the Ether will be forever lost.
If you want to deactivate your contracts, it is preferable to disable them by changing some internal state which causes all functions to throw. This will make it impossible to use the contract and ether sent to the contract will be returned automatically.
Now to answering the question: Inside a constructor, msg.sender
is the
creator. Save it. Then selfdestruct(creator);
to kill and return funds.
Note that if you import "mortal"
at the top of your contracts and declare
contract SomeContract is mortal { ...
and compile with a compiler that already
has it (which includes Remix), then
kill()
is taken care of for you. Once a contract is “mortal”, then you can
contractname.kill.sendTransaction({from:eth.coinbase})
, just the same as my
examples.
Store Ether in a contract¶
The trick is to create the contract with {from:someaddress, value: web3.toWei(3,"ether")...}
Use a non-constant function (req sendTransaction
) to increment a variable in a contract¶
Get a contract to return its funds to you (not using selfdestruct(...)
).¶
This example demonstrates how to send funds from a contract to an address.
See endowment_retriever.
Can you return an array or a string
from a solidity function call?¶
Yes. See array_receiver_and_returner.sol.
What is problematic, though, is returning any variably-sized data (e.g. a
variably-sized array like uint[]
) from a fuction called from within Solidity.
This is a limitation of the EVM and will be solved with the next protocol update.
Returning variably-sized data as part of an external transaction or call is fine.
How do you represent double
/float
in Solidity?¶
This is not yet possible.
Is it possible to in-line initialize an array like so: string[] myarray = ["a", "b"];
¶
Yes. However it should be noted that this currently only works with statically sized memory arrays. You can even create an inline memory array in the return statement. Pretty cool, huh?
Example:
pragma solidity ^0.4.0;
contract C {
function f() returns (uint8[5]) {
string[4] memory adaArr = ["This", "is", "an", "array"];
return ([1, 2, 3, 4, 5]);
}
}
Are timestamps (now,
block.timestamp
) reliable?¶
This depends on what you mean by “reliable”. In general, they are supplied by miners and are therefore vulnerable.
Unless someone really messes up the blockchain or the clock on your computer, you can make the following assumptions:
You publish a transaction at a time X, this transaction contains same
code that calls now
and is included in a block whose timestamp is Y
and this block is included into the canonical chain (published) at a time Z.
The value of now
will be identical to Y and X <= Y <= Z.
Never use now
or block.hash
as a source of randomness, unless you know
what you are doing!
Can a contract function return a struct
?¶
Yes, but only in internal
function calls.
If I return an enum
, I only get integer values in web3.js. How to get the named values?¶
Enums are not supported by the ABI, they are just supported by Solidity. You have to do the mapping yourself for now, we might provide some help later.
What is the deal with function () { ... }
inside Solidity contracts? How can a function not have a name?¶
This function is called “fallback function” and it is called when someone just sent Ether to the contract without providing any data or if someone messed up the types so that they tried to call a function that does not exist.
The default behaviour (if no fallback function is explicitly given) in these situations is to throw an exception.
If the contract is meant to receive Ether with simple transfers, you should implement the fallback function as
function() payable { }
Another use of the fallback function is to e.g. register that your contract received ether by using an event.
Attention: If you implement the fallback function take care that it uses as
little gas as possible, because send()
will only supply a limited amount.
Is it possible to pass arguments to the fallback function?¶
The fallback function cannot take parameters.
Under special circumstances, you can send data. If you take care
that none of the other functions is invoked, you can access the data
by msg.data
.
Can state variables be initialized in-line?¶
Yes, this is possible for all types (even for structs). However, for arrays it should be noted that you must declare them as static memory arrays.
Examples:
pragma solidity ^0.4.0;
contract C {
struct S {
uint a;
uint b;
}
S public x = S(1, 2);
string name = "Ada";
string[4] adaArr = ["This", "is", "an", "array"];
}
contract D {
C c = new C();
}
How do structs work?¶
How do for loops work?¶
Very similar to JavaScript. There is one point to watch out for, though:
If you use for (var i = 0; i < a.length; i ++) { a[i] = i; }
, then
the type of i
will be inferred only from 0
, whose type is uint8
.
This means that if a
has more than 255
elements, your loop will
not terminate because i
can only hold values up to 255
.
Better use for (uint i = 0; i < a.length...
What character set does Solidity use?¶
Solidity is character set agnostic concerning strings in the source code, although UTF-8 is recommended. Identifiers (variables, functions, ...) can only use ASCII.
What are some examples of basic string manipulation (substring
, indexOf
, charAt
, etc)?¶
There are some string utility functions at stringUtils.sol which will be extended in the future. In addition, Arachnid has written solidity-stringutils.
For now, if you want to modify a string (even when you only want to know its length),
you should always convert it to a bytes
first:
pragma solidity ^0.4.0;
contract C {
string s;
function append(byte c) {
bytes(s).push(c);
}
function set(uint i, byte c) {
bytes(s)[i] = c;
}
}
Can I concatenate two strings?¶
You have to do it manually for now.
Why is the low-level function .call()
less favorable than instantiating a contract with a variable (ContractB b;
) and executing its functions (b.doSomething();
)?¶
If you use actual functions, the compiler will tell you if the types or your arguments do not match, if the function does not exist or is not visible and it will do the packing of the arguments for you.
Is unused gas automatically refunded?¶
Yes and it is immediate, i.e. done as part of the transaction.
When returning a value of say uint
type, is it possible to return an undefined
or “null”-like value?¶
This is not possible, because all types use up the full value range.
You have the option to throw
on error, which will also revert the whole
transaction, which might be a good idea if you ran into an unexpected
situation.
If you do not want to throw, you can return a pair:
pragma solidity ^0.4.0;
contract C {
uint[] counters;
function getCounter(uint index)
returns (uint counter, bool error) {
if (index >= counters.length)
return (0, true);
else
return (counters[index], false);
}
function checkCounter(uint index) {
var (counter, error) = getCounter(index);
if (error) {
// ...
} else {
// ...
}
}
}
Are comments included with deployed contracts and do they increase deployment gas?¶
No, everything that is not needed for execution is removed during compilation. This includes, among others, comments, variable names and type names.
What happens if you send ether along with a function call to a contract?¶
It gets added to the total balance of the contract, just like when you send ether when creating a contract.
You can only send ether along to a function that has the payable
modifier,
otherwise an exception is thrown.
Is it possible to get a tx receipt for a transaction executed contract-to-contract?¶
No, a function call from one contract to another does not create its own transaction, you have to look in the overall transaction. This is also the reason why several block explorer do not show Ether sent between contracts correctly.
What is the memory
keyword? What does it do?¶
The Ethereum Virtual Machine has three areas where it can store items.
The first is “storage”, where all the contract state variables reside. Every contract has its own storage and it is persistent between function calls and quite expensive to use.
The second is “memory”, this is used to hold temporary values. It is erased between (external) function calls and is cheaper to use.
The third one is the stack, which is used to hold small local variables. It is almost free to use, but can only hold a limited amount of values.
For almost all types, you cannot specify where they should be stored, because they are copied everytime they are used.
The types where the so-called storage location is important are structs and arrays. If you e.g. pass such variables in function calls, their data is not copied if it can stay in memory or stay in storage. This means that you can modify their content in the called function and these modifications will still be visible in the caller.
There are defaults for the storage location depending on which type of variable it concerns:
- state variables are always in storage
- function arguments are always in memory
- local variables always reference storage
Example:
pragma solidity ^0.4.0;
contract C {
uint[] data1;
uint[] data2;
function appendOne() {
append(data1);
}
function appendTwo() {
append(data2);
}
function append(uint[] storage d) internal {
d.push(1);
}
}
The function append
can work both on data1
and data2
and its modifications will be
stored permanently. If you remove the storage
keyword, the default
is to use memory
for function arguments. This has the effect that
at the point where append(data1)
or append(data2)
is called, an
independent copy of the state variable is created in memory and
append
operates on this copy (which does not support .push
- but that
is another issue). The modifications to this independent copy do not
carry back to data1
or data2
.
A common mistake is to declare a local variable and assume that it will be created in memory, although it will be created in storage:
/// THIS CONTRACT CONTAINS AN ERROR
pragma solidity ^0.4.0;
contract C {
uint someVariable;
uint[] data;
function f() {
uint[] x;
x.push(2);
data = x;
}
}
The type of the local variable x
is uint[] storage
, but since
storage is not dynamically allocated, it has to be assigned from
a state variable before it can be used. So no space in storage will be
allocated for x
, but instead it functions only as an alias for
a pre-existing variable in storage.
What will happen is that the compiler interprets x
as a storage
pointer and will make it point to the storage slot 0
by default.
This has the effect that someVariable
(which resides at storage
slot 0
) is modified by x.push(2)
.
The correct way to do this is the following:
pragma solidity ^0.4.0;
contract C {
uint someVariable;
uint[] data;
function f() {
uint[] x = data;
x.push(2);
}
}
What is the difference between bytes
and byte[]
?¶
bytes
is usually more efficient: When used as arguments to functions (i.e. in
CALLDATA) or in memory, every single element of a byte[]
is padded to 32
bytes which wastes 31 bytes per element.
Is it possible to send a value while calling an overloaded function?¶
It’s a known missing feature. https://www.pivotaltracker.com/story/show/92020468 as part of https://www.pivotaltracker.com/n/projects/1189488
Best solution currently see is to introduce a special case for gas and value and just re-check whether they are present at the point of overload resolution.
Advanced Questions¶
How do you get a random number in a contract? (Implement a self-returning gambling contract.)¶
Getting randomness right is often the crucial part in a crypto project and most failures result from bad random number generators.
If you do not want it to be safe, you build something similar to the coin flipper but otherwise, rather use a contract that supplies randomness, like the RANDAO.
Get return value from non-constant function from another contract¶
The key point is that the calling contract needs to know about the function it intends to call.
Get contract to do something when it is first mined¶
Use the constructor. Anything inside it will be executed when the contract is first mined.
See replicator.sol.
How do you create 2-dimensional arrays?¶
See 2D_array.sol.
Note that filling a 10x10 square of uint8
+ contract creation took more than 800,000
gas at the time of this writing. 17x17 took 2,000,000
gas. With the limit at
3.14 million... well, there’s a pretty low ceiling for what you can create right
now.
Note that merely “creating” the array is free, the costs are in filling it.
Note2: Optimizing storage access can pull the gas costs down considerably, because
32 uint8
values can be stored in a single slot. The problem is that these optimizations
currently do not work across loops and also have a problem with bounds checking.
You might get much better results in the future, though.
What does p.recipient.call.value(p.amount)(p.data)
do?¶
Every external function call in Solidity can be modified in two ways:
- You can add Ether together with the call
- You can limit the amount of gas available to the call
This is done by “calling a function on the function”:
f.gas(2).value(20)()
calls the modified function f
and thereby sending 20
Wei and limiting the gas to 2 (so this function call will most likely go out of
gas and return your 20 Wei).
In the above example, the low-level function call
is used to invoke another
contract with p.data
as payload and p.amount
Wei is sent with that call.
What happens to a struct
‘s mapping when copying over a struct
?¶
This is a very interesting question. Suppose that we have a contract field set up like such:
struct user {
mapping(string => address) usedContracts;
}
function somefunction {
user user1;
user1.usedContracts["Hello"] = "World";
user user2 = user1;
}
In this case, the mapping of the struct being copied over into the userList is ignored as there is no “list of mapped keys”. Therefore it is not possible to find out which values should be copied over.
How do I initialize a contract with only a specific amount of wei?¶
Currently the approach is a little ugly, but there is little that can be done to improve it.
In the case of a contract A
calling a new instance of contract B
, parentheses have to be used around
new B
because B.value
would refer to a member of B
called value
.
You will need to make sure that you have both contracts aware of each other’s presence and that contract B
has a payable
constructor.
In this example:
pragma solidity ^0.4.0;
contract B {
function B() payable {}
}
contract A {
address child;
function test() {
child = (new B).value(10)(); //construct a new B with 10 wei
}
}
Can a contract function accept a two-dimensional array?¶
This is not yet implemented for external calls and dynamic arrays - you can only use one level of dynamic arrays.
What is the relationship between bytes32
and string
? Why is it that bytes32 somevar = "stringliteral";
works and what does the saved 32-byte hex value mean?¶
The type bytes32
can hold 32 (raw) bytes. In the assignment bytes32 samevar = "stringliteral";
,
the string literal is interpreted in its raw byte form and if you inspect somevar
and
see a 32-byte hex value, this is just "stringliteral"
in hex.
The type bytes
is similar, only that it can change its length.
Finally, string
is basically identical to bytes
only that it is assumed
to hold the UTF-8 encoding of a real string. Since string
stores the
data in UTF-8 encoding it is quite expensive to compute the number of
characters in the string (the encoding of some characters takes more
than a single byte). Because of that, string s; s.length
is not yet
supported and not even index access s[2]
. But if you want to access
the low-level byte encoding of the string, you can use
bytes(s).length
and bytes(s)[2]
which will result in the number
of bytes in the UTF-8 encoding of the string (not the number of
characters) and the second byte (not character) of the UTF-8 encoded
string, respectively.
Can a contract pass an array (static size) or string or bytes
(dynamic size) to another contract?¶
Sure. Take care that if you cross the memory / storage boundary, independent copies will be created:
pragma solidity ^0.4.0;
contract C {
uint[20] x;
function f() {
g(x);
h(x);
}
function g(uint[20] y) internal {
y[2] = 3;
}
function h(uint[20] storage y) internal {
y[3] = 4;
}
}
The call to g(x)
will not have an effect on x
because it needs
to create an independent copy of the storage value in memory
(the default storage location is memory). On the other hand,
h(x)
successfully modifies x
because only a reference
and not a copy is passed.
Sometimes, when I try to change the length of an array with ex: arrayname.length = 7;
I get a compiler error Value must be an lvalue
. Why?¶
You can resize a dynamic array in storage (i.e. an array declared at the
contract level) with arrayname.length = <some new length>;
. If you get the
“lvalue” error, you are probably doing one of two things wrong.
- You might be trying to resize an array in “memory”, or
- You might be trying to resize a non-dynamic array.
int8[] memory memArr; // Case 1
memArr.length++; // illegal
int8[5] storageArr; // Case 2
somearray.length++; // legal
int8[5] storage storageArr2; // Explicit case 2
somearray2.length++; // legal
Important note: In Solidity, array dimensions are declared backwards from the way you might be used to declaring them in C or Java, but they are access as in C or Java.
For example, int8[][5] somearray;
are 5 dynamic int8
arrays.
The reason for this is that T[5]
is always an array of 5 T
‘s,
no matter whether T
itself is an array or not (this is not the
case in C or Java).
Is it possible to return an array of strings (string[]
) from a Solidity function?¶
Not yet, as this requires two levels of dynamic arrays (string
is a dynamic array itself).
If you issue a call for an array, it is possible to retrieve the whole array? Or must you write a helper function for that?¶
The automatic getter function for a public state variable of array type only returns individual elements. If you want to return the complete array, you have to manually write a function to do that.
What could have happened if an account has storage value(s) but no code? Example: http://test.ether.camp/account/5f740b3a43fbb99724ce93a879805f4dc89178b5¶
The last thing a constructor does is returning the code of the contract. The gas costs for this depend on the length of the code and it might be that the supplied gas is not enough. This situation is the only one where an “out of gas” exception does not revert changes to the state, i.e. in this case the initialisation of the state variables.
https://github.com/ethereum/wiki/wiki/Subtleties
After a successful CREATE operation’s sub-execution, if the operation returns x, 5 * len(x) gas is subtracted from the remaining gas before the contract is created. If the remaining gas is less than 5 * len(x), then no gas is subtracted, the code of the created contract becomes the empty string, but this is not treated as an exceptional condition - no reverts happen.
What does the following strange check do in the Custom Token contract?¶
require((balanceOf[_to] + _value) >= balanceOf[_to]);
Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range.
For uint256
, this is 0
up to 2**256 - 1
. If the result of some operation on those numbers
does not fit inside this range, it is truncated. These truncations can have
serious consequences, so code like the one
above is necessary to avoid certain attacks.