Documentación de Solidity 0.4.6 en español - 7. Tipos de valor
Puedes seguir aquí todos los capítulos traducidos y la documentación original en inglés.
Solidity es un lenguaje de tipado estático, lo cual significa que el tipo de cada variable (de estado o local), necesita ser especificada en tiempo de compilación. Solidity provee muchos tipos elementales que pueden ser combinados para formar tipos complejos.
Además, los tipos pueden interactuar con otros en expresiones que contengan operadores.
Tipos de Valor
Los siguientes tipos pueden ser también llamados tipos de valor porque las variables de esos tipos siempre son pasadas mediante valores, por ejemplo, son siempre copiadas cuando son usadas como argumentos de función o en asignaciones.
Booleanos
bool
: Los valores posibles son las constantes true
o false
.
Operadores:
!
(negación lógica)
&&
(conjunción lógica, “y”)
||
(disyunción lógica, “o”)
==
(igualdad)
!=
(desigualdad)
Los operadores ||
and &&
aplican las mismas reglas comunes de cortocircuito. Esto significa que la expresión f(x) || g(y)
, if f(x)
es evaluado como true
, g(y)
no será evaluado incluso aunque esto pudiera tener efectos secundarios.
Enteros
int
/ uint
: Enteros firmados y no firmados de varios tamaños. Las claves uint8
a uint256
en saltos de 8 (no firmados de 8 hasta 256 bits) además de int8
hasta int256
. uint
and int
son alias para uint256
e int256
, respectivamente.
Operadores
Comparaciones: <=
, <
, ==
, !=
, >=
, >
(evaluado como bool
)
Operadores de bit: &
, |
, ^
('o' bit a bit exclusivo), ~
(negación bit a bit)
Operadores aritméticos: +
, -
, unario -
, unario +
, *
, /
, %
(resto), **
(exponenciación)
La división siempre se trunca, pero no se trunca si ambos operadores son literales (o expresiones literales) .
Dividir por cero y módulos con cero envían una excepción.
Address
address
: Tiene un valor de 20 bits (tamaño de una dirección Ethereum). Los tipos adress también tienen miembros y sirven como base para todos los contratos.
Operadores:
<=
, <
, ==
, !=
, >=
y >
Miembros de direcciones
balance
y send
Es posible consultar el balance de una dirección usando la propiedad balance
y enviar Ether (en unidades de wei) a una dirección usando la función send
.
It is possible to query the balance of an address using the property balance and to send Ether (in units of wei) to an address using the send function:
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.send(10);
Observación:
Six
es la dirección de un contrato, su código (más específicamente, su función de retroceso, si está presente) será ejecutado junto con la llamadasend
(esta es una limitación de la EVM y no puede ser evitada). Si esa ejecución corre sin gas o falla de alguna manera, la transacción de Ether será revertida. En este caso,send
devuevefalse
.
Advertencia:
Hay algunos peligros al usarsend
: La transferencia falla si la profundidad de la pila está a 1024 (esto siempre puede ser forzado por el llamador) y también falla si el recipiente corre sin gas. Así que en orden de hacer seguras las transacciones de Ether, comprueba siempre el valor de retorno o incluso mejor: utilice un patrón en el que el destinatario retire el dinero.
call
, callcode
y delegatecall
Además para interactuar con contratos que no se adhieren al ABI, es provista la función call
la cual toma un número arbitrario de argumentos de cualquier tipo. Esos argumentos son acolchados a 32 bytes y concatenados. Una excepción es el caso de que el primer argumento está codificado a exactamente 4 bytes. En este caso, no es alcolchado para permitir el uso de firmas de funciones aquí.
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
devuelve un booleano indicando que la función invocada ha terminado (true
) o ha causado una excepción en la EVM (false
). No es posible acceder a la información actual devuelta (para esto necesitaríamos conocer la codificación por adelantado).
De una manera similar puede ser usada la función delegatecall
: La diferencia es que sólo es usado el código de una dirección dada, todos los otros aspectos (almacenamiento, balance...) son tomados del contrato actual. El propósito de delegatecall
es usar código de biblioteca almacenado en otro contrato. El usuario tiene que asegurarse que la capa de almacenamiento en ambos contratos es adecuada para ser usada por delegatecall. Antes de homestead, sólo una variante limitada llamada callcode
estaba disponible la cual no proveía acceso al original msg.sender
y los valores de msg.value
.
Las tres funciones call
, delegatecall
y callcode
son funciones de bajo nivel y sólo deberían ser usadas como último recurso, ya que rompen la seguridad de tipado en Solidity.
Observación:
Todos los contratos herencian los miembros de su dirección, así que es posible consultar el balance del contrato actual usando this.balance.
Advertencia:
Todas estas funciones son funciones de bajo nível y deberían ser usadas con cuidado. Específicamente, cualquier contrato desconocido podría ser malicioso y si lo llamas, tu entregas todo el control a ese contrato el cual podría llamar de vuelta adentro de tu contrato, así que estate preparado para que cambie tus variables de estado cuando devuelva la llamada.
Arrays de bytes de tamaño fijo
bytes1, bytes2, bytes3, ..., bytes32. byte is an alias for bytes1.
Operadores:
Comparaciones: <=
, <
, ==
, !=
, >=
, >
(evaluado como bool
)
Operadores de bit: &
, |
, ^
(bitwise exclusive or),~
(negación bit a bit)
Acceso al índice: Si x
es del tipo bytesI
, entonces x[k]
para 0 <= k < I
devuelve k
th byte (sólo-lectura).
Miembros:
.length
produce la longitud fija del byte array (sólo-lectura).
Arrays de bytes de tamaño dinámico
bytes
:
Arrays de bytes de tamaño dinámico. ¡No es un tipo valor!
string
:
Cadena de texto de tamaño dinámico codificada en UTF-8. ¡No es un tipo valor!
Como regla de oro, usa bytes
para datos de bytes en bruto de longitud arbitraria y string
para datos de cadenas de longitud arbitraria. Si puede limitar la longitud a determinado número de bytes, usa no de los bytes
a bytes32
debido a que son mucho más baratos.
Números 'Fixed Point'
COMING SOON...
Literales racionales y enteros
Todos los números literales conservan una precisión arbitraria hasta que son convertidos a un tipo no literal (por ejemplo, usándolo junto a un tipo no literal). Esto significa que los cómputos no se desbordan pero también que las divisiones no se truncan.
Por ejemplo, (2**800 + 1) - 2**800
resulta en la constante 1 (de tipo uint8), aunque los resultados intermedios ni siquiera se ajustan al tamaño de palabra de la máquina. Además, .5 * 8
resulta en el entero 4 (sin embargo se utilizaron ahí no enteros.
Si el resultado no es un entero, se usa un tipo apropiado fijo o no fijo cuyo número de bits fraccionales es tan largo como requiere (aproximadamente el número racional en el peor de los casos).
En var x = 1/4:
, x
recibirá el tipo ufixed0x8
mientras en var x=1/3
recibirá el tipo ufixed0x256
porque 1/3
no es representable finítamente en binario y será apróximado así.
Cualquier operador que puede ser aplicado a números enteros también puede serlo a expresiones literales siempre y cuando los operandos sean enteros. Si alguno de los dos es fraccional, las operaciones con bit son deshabilitadas, así como la exponenciación si el exponente es fraccional (porque esto puede resultar en un número no racional).
Observación:
La mayoría de las fracciones decimales finitas como5.3743
no son representables finitamente en binario. El tipo correcto para5.3743
esufixed8x248
porque este permite la mejor aproximación al número. Si quieres usar el número con tipos comoufixed
(por ejemplo,ufixed128x128
), tienes que especificar explícitamente la precisión deseada:x + ufixed(5.3743)
.
Advertencia:
Dividir enteros literales solía truncar en versiones anteriores, pero ahora serán convertidas en un número racional, por ejemplo5/2
no es igual a2
, si no a2.5
.
Observación:
Las expresiones literales son convertidas en un tipo permanente tan pronto como son usadas con otras expresiones. Aún cuando conocemos que el valor de una expresión asignada ab
en el siguiente ejemplo evalúa a un entero, sigue usando tipos 'fixed point' (y números literales no racionales) entre ellos, por lo que el código no compila.
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
Cadenas literales
Las cadenas literales son escritas con comillas simples o dobles ("foo"
o 'bar'
). Como los enteros literales, su tipo puede variar, pero son implícitamente convertibles a bytes1
, ..., bytes32
, si encajan en bytes y en cadenas.
Las cadenas literales soportan caracteres de escape, tales como \n
, \xNN
y \uNNNN
. \xNN
toma un valor hexadecimal e inserta el byte apropiado, mientras \uNNNN
toma un Unicode y lo inserta en una secuencia UTF-8.
Literales Hexadecimales
Los literales hexadecimales son prefijados con la clave hex
y adjuntados en comillas simples o dobles (hex"001122FF"
). Su contenido debe ser una cadena hexadecimal y su valor será la representación binaria de esos valores.
Los literales hexadecimales se comportan como las cadenas literales y tienen las mismas restricciones de convertibilidad.
Enums
Los enums son una manera de crear un tipo definido por el usuario en Solidity. Son convertibles explícitamente a y desde un tipo entero pero su conversión implícita no está permitida. Las conversiones explícitas comprueban el rango de valor en tiempo de ejecución y un fallo causa una excepción. Los enums necesitan al menos de 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;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more values,
// `uint16` will be used and so on.
function getChoice() returns (ActionChoices) {
return choice;
}
function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}
Tipos función
Son tipos de funciones. Las variables de un tipo función pueden ser asignadas desde funciones y los parámetros de función de un tipo función pueden ser usados para pasar funciones y retornar funciones desde llamadas a funciones. Los tipos función vienen de dos sabores - funciones internas o externas:
Las funciones internas sólo pueden ser usadas dentro del contrato actual (más específicamente, dentro de la unidad de código actual, el cual también incluye librerías internas de funciones y funciones heradadas) debido a que no puede ser ejecutado fuera del contexto del actual contrato. El llamar a una función interna se realiza saltando 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 forma de función, y pueden ser pasadas via y retornadas desde llamadas externas de función
Los tipos función son enumerados como sigue:
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]
En contraste con los tipos de parámetro, los tipos retornados no pueden estar vacíos, si el tipo función no debería retornar nada, la parte returns (<return types>)
entera tiene que ser omitida.
Por defecto, los tipos función son internos, así que la clave internal
puede ser omitida.
Hay dos maneras de acceder a una función en el contrato actual: directamente mediante su nombre f
, o usando this.f
. El anterior resultará en una función interna y el último en una externa.
Si un variable de tipo función no es inicializada, llamar resultará en una ecepción. Lo mismo sucede si llamas a una función después de usar delete
en ella.
Si son utilizadas tipos función externos fuera del contexto de Solidity, son tratados como un tipo función, el cual codifica la dirección seguida por la función identificador juntos en un sólo tipo 'bytes24`.
Ejemplo que muestra cómo utilizar tipos función internos:
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) returns (uint) f)
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) returns (uint) f
)
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) 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) return (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 usa tipos función externos:
contract Oracle {
struct Request {
bytes data;
function(bytes) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes) external callback) {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = 0x1234567; // known contract
function buySomething() {
oracle.query("USD", oracleResponse);
}
function oracleResponse(bytes response) {
if (msg.sender != oracle) throw;
// Use the data
}
}
Observa que lambda o las funciones en línea están planeadas pero no soportadas aún.