¿Es conveniente usar mapeos como instancias temporales de KeyValue?

Considere el siguiente contrato que ilustra el problema. Aquí tenemos una matriz dinámica de asignaciones, add()está destinada a agregar una nueva asignación nueva al final de la matriz, devolver el valor de la clave de asignación 0y cambiar ese valor a true. remove()a su vez está destinado a eliminar el último mapeo de una matriz.

contract ClearMapping {
    mapping(uint => bool)[] state;

    // 1. add -> false
    // 2. remove
    // 3. add -> true
    function add() returns (bool) {
        uint pos = state.length++;
        bool curr = state[pos][0];
        state[pos][0] = true;
        return curr;
    }

    function remove() {
        state.length--;
    }
}

Uno puede pensar que eliminar un elemento de mapeo de una matriz borra efectivamente ese mapeo, y que agregar un nuevo mapeo en lugar del anterior tendrá un key* -> falseelemento nuevo, todo. Resulta que no es cierto. Lo mismo sucede si intenta intercambiar elementos que tienen asignaciones dentro, todo se intercambiará excepto las asignaciones.

La única solución que veo para lidiar con esto es eliminar/reasignar manualmente todas las teclas de mapeo usadas, pero se vuelve costoso en términos de uso de gas muy rápido.

Las preguntas que busco responder son:

  1. ¿Es esto un error en EVM/Solidity?
  2. ¿Existe una forma conveniente/efectiva de borrar/intercambiar asignaciones?
  3. ¿No debería usar asignaciones como instancias temporales de KeyValue?

¡Gracias por la atención!

Respuestas (1)

Para responder a su pregunta, permítame explicar cómo se ve realmente la pila EVM: en sí misma es solo un mapa de una clave a un valor, ambos de 32 bytes de longitud.

un mapa de solidez, mapas desde sha3(mapId . key) a un valor dado en la pila evm. Esa es también la razón por la que uno no puede iterar a través de todas las claves en un mapa, porque están "aleatorizadas" a través de toda la pila de evm.

una matriz de solidez se asigna desde sha3 (arrayId) + índice a un valor. Aquí podemos iterar si conocemos el arrayId simplemente incrementando el índice.

(Aquí no estoy realmente seguro de si la longitud de una matriz también se guarda).

Lo siguiente es solo una especulación, ya que estoy completamente seguro de cómo la solidez resuelve esto: si ahora tiene una matriz de mapas, lo que realmente hace es mapId = sha3 (arrayId) + index. Puede iterar a través de ellos. Sin embargo, si guarda algo en un mapa, haga lo siguiente:

sha3((sha3(arrayId) + index) . key ) = value

Pero al eliminar el último elemento del mapa, pierde la identificación de matriz para el mapa, pero debido a que uno no puede iterar a través de los mapas sin conocer las claves, su valor se conserva.

Sin embargo, los arreglos múltiples no tienen este problema:

import "dapple/test.sol";

contract A is Test {
  uint[][] multiarray;

  function testMultiArray() {
    //@log multiarray length: `uint multiarray.length`
    //@log incrementing multiarray
    multiarray.length++;
    //@log multiarray length: `uint multiarray.length`
    //@log multiarray[0] length: `uint multiarray[0].length`
    //@log incrementing multiarray[0]
    multiarray[0].length++;
    multiarray[0].length++;
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log set value to 1
    multiarray[0][0] = 1;
    multiarray[0][1] = 1;
    //@log multiarray[0] length: `uint multiarray[0].length`
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log multiarray[0][1]: `uint multiarray[0][1]`
    //@log decrementing multiarray
    multiarray.length--;
    //@log multiarray[0] length: `uint multiarray.length`
    //@log incrementing multiarray
    multiarray.length++;
    //@log multiarray[0] length: `uint multiarray[0].length`
    multiarray[0].length++;
    multiarray[0].length++;
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log multiarray[0][1]: `uint multiarray[0][1]`
  }
}

Producirá la siguiente salida:

  test multi array
  LOG:  multiarray length: 0
  LOG:  incrementing multiarray
  LOG:  multiarray length: 1
  LOG:  multiarray[0] length: 0
  LOG:  incrementing multiarray[0]
  LOG:  multiarray[0][0]: 0
  LOG:  set value to 1
  LOG:  multiarray[0] length: 2
  LOG:  multiarray[0][0]: 1
  LOG:  multiarray[0][1]: 1
  LOG:  decrementing multiarray
  LOG:  multiarray[0] length: 0
  LOG:  incrementing multiarray
  LOG:  multiarray[0] length: 0
  LOG:  multiarray[0][0]: 0
  LOG:  multiarray[0][1]: 0
¡Gracias por la explicación! Ahora puedo decir que con esta arquitectura de almacenamiento no es un error (pregunta 1). Y sabía que está bien para las matrices, aunque no se pueden sustituir efectivamente todos los tipos de asignaciones con matrices. ¿Qué pasa con la pregunta 2? En cuanto a 3, decidí no usar asignaciones en entidades temporales por ahora.
2. sencillo => no. Si desea borrar un mapeo, debe recordar e iterar a través de todas las claves y eliminarlo manualmente con delete map[key]. Hay un patrón iterable que consiste en un mpping y una matriz, que le permite iterar a través de su mapa, que puede usarse para eliminar los elementos. También puede crear sus propias estructuras de datos para eliminar elementos mapeados, pero evm no tiene compilación de recolección de basura, tendrá que hacerlo manualmente.
Eso es lo que estaba haciendo después de descubrir esto. Pero el uso de gas está aumentando dramáticamente, lo que no favorece la producción. Tal vez hay una manera de sustituirlos? Por ejemplo, para sustituir key => booluno puede usar uint[]una colección de lotes de 256 bools cada uno. Pero ¿ key => uinty los demás?