¿Están los guiones bajos `_` en el código de los modificadores o solo tienen la intención de verse bien?

Veo a menudo _en modificadores

modifier onlyOwner() {
    if (msg.sender != owner) throw;
    _
}

¿Ejecuta algún código o está destinado a hacer que el código sea más fácil de leer?

Respuestas (1)

Actualización 12 de octubre de 2016

A partir de la versión 0.4.0+ de Solidity, ahora debe agregar un punto y coma después de _. Ver Solidez - Versión 0.4.0 :

  • Cambiar _a _;en modificadores.

Las siguientes pruebas solo funcionan en Solidity < v 0.4.0.



Resumen

  • El código de la función que se está modificando se inserta donde _se coloca en el modificador.
  • Puede agregar más de una _s en el código modificador. Y el código de la función que se modifica se inserta en cada lugar donde _se encuentra en el modificador. ver modifier checkThree_ Esto puede evitarse con versiones posteriores del solccompilador.
  • Los modificadores se llaman en la secuencia en que fueron definidos ( checkOne checkTwo checkThree) y al final de la función, se llaman a la inversa. Los modificadores parecen aplicarse como una pila. En este ejemplo de todos modos.



Detalles

De Solidity Features - Modificadores de función :

Los modificadores PT se pueden usar para cambiar fácilmente el comportamiento de las funciones, por ejemplo, para verificar automáticamente una condición antes de ejecutar la función. Son propiedades heredables de los contratos y pueden ser anuladas por contratos derivados.

contract owned {
  function owned() { owner = msg.sender; }
  address owner;

  // This contract only defines a modifier but does not use it - it will
  // be used in derived contracts.
  // The function body is inserted where the special symbol "_" in the
  // definition of a modifier appears.
  modifier onlyowner { if (msg.sender == owner) _ }
}


Aquí hay un ejemplo de EtherScan.io - The DAO - Código fuente .

El modificador onlyTokenholdersrestringe que las funciones "modificadas" sean ejecutadas por personas que no poseen tokens.

modifier onlyTokenholders {
    if (balanceOf(msg.sender) == 0) throw;
        _
}

Aquí está la vote(...)función con el onlyTokenHoldersmodificador:

function vote(
    uint _proposalID,
    bool _supportsProposal
) onlyTokenholders noEther returns (uint _voteID) {

    Proposal p = proposals[_proposalID];
    if (p.votedYes[msg.sender]
        || p.votedNo[msg.sender]
        || now >= p.votingDeadline) {

        throw;
    }

El código dentro de la vote(...)función solo se ejecuta si la verificación del modificador no arroja un error de la declaración if (balanceOf(msg.sender) == 0) throw;. El _representa el cuerpo de la vote(...)función.


De Learn X in Y minutes - Where X=Solidity , aquí hay un ejemplo donde _no está al final de la función modificadora:

// underscore can be included before end of body,
// but explicitly returning will skip, so use carefully
modifier checkValue(uint amount) {
    _
    if (msg.value > amount) {
        msg.sender.send(amount - msg.value);
    }
}


Tomemos '_' para una prueba

Aquí hay un código para probar _:

contract TestModifier {

    string[] public messages;
    uint256 testVariable;

    function numberOfMessages() constant returns (uint256) {
        return messages.length;
    }

    modifier checkOne {
        messages.push("checkOne - 1");
        if (testVariable == 123) 
            throw;
        _
        messages.push("checkOne - 2");
        if (testVariable == 123) 
            throw;
    }

    modifier checkTwo {
        messages.push("checkTwo - 1");
        if (testVariable == 123) 
            throw;
        _
        messages.push("checkTwo - 2");
        if (testVariable == 123) 
            throw;
    }

    modifier checkThree {
        messages.push("checkThree - 1");
        if (testVariable == 123) 
            throw;
        _
        messages.push("checkThree - 2");
        if (testVariable == 123) 
            throw;
        _
        messages.push("checkThree - 3");
        if (testVariable == 123) 
            throw;
    }

    function test() checkOne checkTwo checkThree returns (uint256) {
        messages.push("test - 1");
        testVariable = 345;
        messages.push("test - 2");
        return testVariable;
    }
}

Aplanado el código

> var testModifierSource='contract TestModifier { string[] public messages; uint256 testVariable; function numberOfMessages() constant returns (uint256) { return messages.length; } modifier checkOne { messages.push("checkOne - 1"); if (testVariable == 123)  throw; _ messages.push("checkOne - 2"); if (testVariable == 123)  throw; } modifier checkTwo { messages.push("checkTwo - 1"); if (testVariable == 123)  throw; _ messages.push("checkTwo - 2"); if (testVariable == 123)  throw; } modifier checkThree { messages.push("checkThree - 1"); if (testVariable == 123)  throw; _ messages.push("checkThree - 2"); if (testVariable == 123)  throw; _ messages.push("checkThree - 3"); if (testVariable == 123)  throw; } function test() checkOne checkTwo checkThree returns (uint256) { messages.push("test - 1"); testVariable = 345; messages.push("test - 2"); return testVariable; }}'
undefined

Contrato insertado en la cadena de bloques:

> var testModifierCompiled = web3.eth.compile.solidity(testModifierSource);
undefined
> var testModifierContract = web3.eth.contract(testModifierCompiled.TestModifier.info.abiDefinition);
var testModifier = testModifierContract.new({
    from:web3.eth.accounts[0], 
    data: testModifierCompiled.TestModifier.code, gas: 1000000}, 
    function(e, contract) {
      if (!e) {
        if (!contract.address) {
          console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
        } else {
          console.log("Contract mined! Address: " + contract.address);
          console.log(contract);
        }
    }
})
...
Contract mined! Address: 0xd2ca2d34da6e50d28407f78ded3a07962b56181c
[object Object]

Envió una transacción para llamar a la test()función:

> testModifier.test(eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testModifierCompiled.TestModifier.code,
  gas: 1000000
});

Comprueba los resultados:

> var i;
> for (i = 0; i < testModifier.numberOfMessages(); i++) {
    console.log(testModifier.messages(i));
}
checkOne - 1
checkTwo - 1
checkThree - 1
test - 1
test - 2


Sacar la Declaración de Devolución en test():

Eliminé la declaración de devolución, por lo que el código fuente testes:

function test() checkOne checkTwo checkThree returns (uint256) {
    messages.push("test - 1");
    testVariable = 345;
    messages.push("test - 2");
    // return testVariable;
}

Y volvió a ejecutar la prueba para producir los siguientes resultados:

var i;
undefined
> for (i = 0; i < testModifier.numberOfMessages(); i++) {
..     console.log(testModifier.messages(i));
.. }
checkOne - 1
checkTwo - 1
checkThree - 1
test - 1
test - 2
checkThree - 2
test - 1
test - 2
checkThree - 3
checkTwo - 2
checkOne - 2
undefined
re: El _ representa el cuerpo de la función vote(...). Representa como en lo que es más fácil de leer? He estado escribiendo modificadores sin el _ y no parece ser un "símbolo especial" que haga algo en el EVM, parece ser más una cuestión estética. ¿Tiene algo que ver con cómo se ejecuta el código?
Incluso con tu larga respuesta, todavía no entiendo para qué se usa... lo siento. ¿Tiene el mismo significado que en lenguaje Swift?
@oIG, algunos resultados de pruebas interesantes. @Nicolas Massart, ¿cómo funciona el modificador en Swift?
@dor Bokky lo dijo claramente: le dice a la función a dónde ir. Si su modificador es {action 1; _; action2;}, cuando lo llame, realizará la acción 1, luego la función, luego la acción 2. Si no lo especifica, asumo que el guión bajo se coloca implícitamente al final del modificador.
Esta es una respuesta muy buena y detallada y debe aceptarse. :(
Pienso modifiercomo decoratoren python.
"lanzar" está en desuso en favor de "revertir ()
Esto me dejó muy claro para entender dónde se inserta el código no modificador, cómo se ejecuta el flujo de modificadores y cuándo no usar la declaración de retorno si usamos múltiples modificadores. ¡Gracias!
Lo que obtuve es bastante diferente de usted, lo que podría deberse a las diferencias de versión, pero veo su punto de ilustrar cómo "_" se traduce en comandos en la pila.
TL;RD; Esa es una forma elegante de explicar una devolución de llamada.