¿Cómo construyo una llamada a otro contrato usando ensamblado en línea?

Me gustaría llamar a un contrato y tratar los valores devueltos manualmente usando el ensamblaje EVM en Solidity. Por ejemplo, esto debería simplemente sumar dos números.

contract Test1 {
    function add(int a, int b) returns(int){  //Simply add the two arguments and return
        return a+b;
    }
    function() returns (int){  //If the function signature doesn't check out, return -1
        return -1;
    }
}

contract Test2 {
    Test1 test1;

    function Test2(){  //Constructor function
        test1 = new Test1();  //Create new "Test1" function
    }

    function test(int a, int b) constant returns (int c){
        address addr = address(test1);  //Place the test1 address on the stack
         bytes4 sig = bytes4(sha3("add(int256,int256)")); //Function signature

        assembly {
            let x := mload(0x40)   //Find empty storage location using "free memory pointer"
            mstore(x,sig) //Place signature at begining of empty storage 
            mstore(add(x,0x04),a) //Place first argument directly next to signature
            mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

            call(5000, addr, 0, //Issue call, providing 5k gas and 0 value to "addr"
            x, 0x44, add(x,0x80), 0x20) //Inputs start at location "x" and are 68 bytes long, outputs start 128 bytes after x, and are 32 bytes long
            c := mload(add(x,0x80)) //Assign output value to c
            mstore(0x40,add(x,0x100)) // Set storage pointer to empty space
        }
    }

    function test2(int a, int b) constant returns(int c){ //Make sure the Test1 function works properly
        return test1.add(a,b); // (It does)
    }
}

El problema es que esto devuelve un error de falta de gas, que se origina en la call...línea.

¿Cómo puedo solucionar este problema?

No es un comentario sobre su problema, sino solo curiosidad por saber para qué sirve ensamblar en Solidity y por qué no codificar en Solidity, excepto para el desafío mental.
Hay algunas razones aquí , y en este caso es porque necesito recuperar una matriz de bytes de tamaño dinámico de otro contrato, lo que no se puede hacer automáticamente ya que EVM necesita asignar memoria para devolver datos de antemano.
Gracias, muy interesante incluso si está fuera de mi alcance escribir un código de tan bajo nivel 😥
Realmente debería intentarlo, es realmente útil e incluso las cosas simples como copiar la memoria en nuevas variables facilitan mucho el manejo de matrices y cadenas.
Quizás, algún día, quién sabe, cuando tenga mucha confianza...

Respuestas (1)

El error se debió a un elemento no manejado en la pila que dejó el callcódigo de operación. El código relevante de trabajo y optimizado está aquí:

    assembly {
        let x := mload(0x40)   //Find empty storage location using "free memory pointer"
        mstore(x,sig) //Place signature at begining of empty storage 
        mstore(add(x,0x04),a) //Place first argument directly next to signature
        mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

        let success := call(      //This is the critical change (Pop the top stack value)
                            5000, //5k gas
                            addr, //To addr
                            0,    //No value
                            x,    /Inputs are stored at location x
                            0x44, //Inputs are 68 bytes long
                            x,    //Store output over input (saves space)
                            0x20) //Outputs are 32 bytes long

        c := mload(x) //Assign output value to c
        mstore(0x40,add(x,0x44)) // Set storage pointer to empty space
    }

Gracias a @chriseth por señalar mi error.

EDITAR:

Como señaló @Ilan, la final mstoreno es estrictamente necesaria, ya que no nos importa mantener esa memoria asignada. Si los datos devueltos son un objeto de montón como una matriz, debe asegurarse de que la memoria permanezca asignada.

El quinto parámetro callse mide en bytes, no en bits. SE no me deja editar para corregir porque no es "un cambio lo suficientemente grande" a pesar de que tiene un impacto masivo en la precisión de la respuesta. :/
Buena captura, editado.
Pregunta de @TjadenHess: al configurar el puntero de almacenamiento en un espacio vacío. ya que la entrada ya no es necesaria. puedes usar x + 0x20 así:mstore(0x40,add(x,0x20));
En realidad, no necesita configurarlo en absoluto, ya que ya cargó el valor en la pila
@TjadenHess después de jugar más con el código, hay 3 problemas aquí. 1. Establecer la salida sobre la entrada no funcionará en algunas funciones, donde algún valor de salida puede establecerse antes de usar la entrada por última vez. 2. El puntero de memoria libre debe configurarse antes de llamar a otra función que también podría usarlo. 3. después de que se completó la llamada de función. el puntero de memoria libre debe volver a establecerse en x, que es la posición inicial que tenía antes de que ocurriera la llamada a la función.
1) Cierto, pero ese sería un patrón bastante inusual. De esta manera se ahorra gasolina y es bastante común en la práctica. 2) La memoria es de llamada local, no de contrato local, por lo que esto nunca puede suceder. 3) Cierto, como se mencionó anteriormente
@TjadenHess, ¿puede darnos un poco más de información sobre el error? Me estoy involucrando con el ensamblaje en línea. Sin asignar la salida de llamada a success, ¿qué sucede exactamente? ¿Se carga la siguiente salida para ctomar el booleano de éxito como un argumento de entrada (junto con x) y el error? Muchas gracias.
Exactamente lo que sucede depende de la versión del compilador. Cualquier cosa después de ~ 4.0 simplemente no se compilará con este error. Básicamente, las variables de ensamblaje corresponden a ubicaciones en la pila. Cuando usamos la variable, el compilador genera una swap noperación donde nse indica qué tan abajo en la pila está la variable. En sol ~3.5, cuando dejamos un elemento adicional en la pila, las direcciones se desvían en 1 y, por lo tanto, el argumento de la siguiente función es el 1 anterior xen la pila, lo que da una dirección de memoria no válida.