¿Existen patrones de almacenamiento sencillos y bien resueltos para Solidity?

La organización de datos simple y apropiada puede desafiar a los recién llegados a Solidity. Quiere que organicemos todo de formas a las que muchos de nosotros no estamos acostumbrados.

¿Existen patrones generales bien resueltos para la organización rutinaria de datos en cadena?

¿Qué tipo de almacenamiento? Observo que aún no hay un almacenamiento ordenado en los ejemplos.
Solidity CRUD Library (2019) implementa estructuras asignadas con eliminación: medium.com/robhitchens/solidity-crud-epilogue-e563e794fde

Respuestas (4)

Aquí hay algunos patrones simples y útiles en orden creciente de utilidad.

Los registros de eventos se omiten por brevedad. En la práctica, es deseable emitir eventos para cada cambio de estado importante.

Lista simple usando matriz

Fortalezas

  • Orden cronológico confiable
  • Proporciona un conteo
  • Acceso aleatorio por número de fila (no Id)

debilidades

  • Sin acceso aleatorio por Id.
  • Sin garantía de unicidad
  • Sin verificación de duplicados
  • Crecimiento descontrolado de la lista

Ejemplo:

pragma solidity ^0.4.6;

contract simpleList {

  struct EntityStruct {
    address entityAddress;
    uint entityData;
    // more fields
  }

  EntityStruct[] public entityStructs;

  function newEntity(address entityAddress, uint entityData) public returns(uint rowNumber) {
    EntityStruct memory newEntity;
    newEntity.entityAddress = entityAddress;
    newEntity.entityData    = entityData;
    return entityStructs.push(newEntity)-1;
  }

  function getEntityCount() public constant returns(uint entityCount) {
    return entityStructs.length;
  }
}

Mapeo con Struct

Fortalezas

  • Acceso aleatorio por ID único
  • Garantía de unicidad de identificación
  • Encierre matrices, asignaciones, estructuras dentro de cada "registro"

debilidades

  • No se pueden enumerar las claves
  • No puedo contar las llaves
  • Necesita una verificación manual para distinguir un registro predeterminado de un registro explícitamente "todo 0"

Ejemplo:

contract mappingWithStruct {

  struct EntityStruct {
    uint entityData;
    bool isEntity;
  }

  mapping (address => EntityStruct) public entityStructs;

  function isEntity(address entityAddress) public constant returns(bool isIndeed) {
    return entityStructs[entityAddress].isEntity;
  }

  function newEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(isEntity(entityAddress)) revert(); 
    entityStructs[entityAddress].entityData = entityData;
    entityStructs[entityAddress].isEntity = true;
    return true;
  }

  function deleteEntity(address entityAddress) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].isEntity = false;
    return true;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }
}

Matriz de estructuras con identificadores únicos

Fortalezas

  • Acceso aleatorio por número de fila
  • Garantía de unicidad de Id.
  • Adjunte matrices, asignaciones y estructuras con cada "registro"

debilidades

  • Sin acceso aleatorio por Id.
  • Crecimiento descontrolado de la lista

Ejemplo:

contract arrayWithUniqueIds {

  struct EntityStruct {
    address entityAddress;
    uint entityData;
  }

  EntityStruct[] public entityStructs;
  mapping(address => bool) knownEntity;

  function isEntity(address entityAddress) public constant returns(bool isIndeed) {
    return knownEntity[entityAddress];
  }

  function getEntityCount() public constant returns(uint entityCount) {
    return entityStructs.length;
  }

  function newEntity(address entityAddress, uint entityData) public returns(uint rowNumber) {
    if(isEntity(entityAddress)) revert();
    EntityStruct memory newEntity;
    newEntity.entityAddress = entityAddress;
    newEntity.entityData = entityData;
    knownEntity[entityAddress] = true;
    return entityStructs.push(newEntity) - 1;
  }

  function updateEntity(uint rowNumber, address entityAddress, uint entityData) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    if(entityStructs[rowNumber].entityAddress != entityAddress) revert();
    entityStructs[rowNumber].entityData    = entityData;
    return true;
  }
}

Estructuras asignadas con índice

Fortalezas

  • Acceso aleatorio por ID único o número de fila
  • Garantía de unicidad de Id.
  • Encierre matrices, asignaciones y estructuras dentro de cada "registro"
  • La lista mantiene el orden de declaración
  • Contar los registros
  • enumerar los identificadores
  • Eliminación "suave" de un elemento configurando un valor booleano

debilidades

  • Crecimiento descontrolado de la lista

Ejemplo:

contract MappedStructsWithIndex {

  struct EntityStruct {
    uint entityData;
    bool isEntity;
  }

  mapping(address => EntityStruct) public entityStructs;
  address[] public entityList;

  function isEntity(address entityAddress) public constant returns(bool isIndeed) {
      return entityStructs[entityAddress].isEntity;
  }
  
  function getEntityCount() public constant returns(uint entityCount) {
    return entityList.length;
  }

  function newEntity(address entityAddress, uint entityData) public returns(uint rowNumber) {
    if(isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    entityStructs[entityAddress].isEntity = true;
    return entityList.push(entityAddress) - 1;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData    = entityData;
    return true;
  }
}

Estructuras asignadas con índice habilitado para eliminar

Fortalezas

  • Acceso aleatorio por ID único o número de fila
  • Garantía de unicidad de Id.
  • Encierre arreglos, mapeos y estructuras dentro de cada "registro"
  • Contar los registros
  • enumerar los identificadores
  • Controle lógicamente el tamaño de la lista activa con la función de eliminación

debilidades

  • Complejidad de código marginalmente aumentada
  • Costos de almacenamiento marginalmente más altos
  • La lista de claves está intrínsecamente desordenada

ACTUALIZACIÓN, 2019

Este patrón está disponible como biblioteca para Solidity 0.5.1: https://medium.com/@robhitchens/solidity-crud-epilogue-e563e794fde , https://github.com/rob-Hitchens/UnorderedKeySet

Ejemplo:

contract mappedWithUnorderedIndexAndDelete {

  struct EntityStruct {
    uint entityData;
    uint listPointer;
  }

  mapping(address => EntityStruct) public entityStructs;
  address[] public entityList;

  function isEntity(address entityAddress) public constant returns(bool isIndeed) {
    if(entityList.length == 0) return false;
    return (entityList[entityStructs[entityAddress].listPointer] == entityAddress);
  }

  function getEntityCount() public constant returns(uint entityCount) {
    return entityList.length;
  }

  function newEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    entityStructs[entityAddress].listPointer = entityList.push(entityAddress) - 1;
    return true;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }

  function deleteEntity(address entityAddress) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    uint rowToDelete = entityStructs[entityAddress].listPointer;
    address keyToMove   = entityList[entityList.length-1];
    entityList[rowToDelete] = keyToMove;
    entityStructs[keyToMove].listPointer = rowToDelete;
    entityList.length--;
    return true;
  }

}

Este último tiene una explicación aquí: https://medium.com/@robhitchens/solidity-crud-part-2-ed8d8b4f74ec#.ekc22r5lf

y aquí: https://bitbucket.org/rhitchens2/soliditycrud/src/83703dcaf4d0c4b0d6adc0377455c4f257aa29a7/docs/?at=master

Ejemplo de árbol de carpetas: ¿Cómo podemos organizar el almacenamiento de una carpeta o un árbol de objetos en Solidity?

El ejemplo de lista enlazada muestra una forma de mantener una lista ordenada usando una biblioteca. https://github.com/ethereum/dapp-bin/blob/master/library/linkedList.sol 0

Los comentarios no son para una discusión extensa; esta conversación se ha movido a chat .

Agregando a la respuesta de Rob, use revert() como una alternativa de throw. Desde la versión 0.4.13, la palabra clave throw está obsoleta y se eliminará gradualmente en el futuro. Lea aquí para más información: exigir, afirmar y revertir en solidez.

Entonces, como ejemplo, deberías cambiar

if(isEntity(entityAddress)) throw;

a

if(isEntity(entityAddress)) revert();

en el código anterior proporcionado por Rob.

Aquí están los ejemplos de código actualizados usando revert(): ethfiddle.com/PgDM-drAc9
¡Gracias, James! Estoy seguro de que ayudará a la gente. También se podría usar, require()pero eso significaría invertir todas las reglas... if(bad) revert()=>require(!bad)
¿Cuál es la principal diferencia entre throwy revert()? ¿Hacen lo mismo, revirtiendo todos los estados? @Abhishek Sinha && @Rob Hitchens
Comenzando solc 0.4.13, y obsoleto . revert_ Se diferencian ligeramente del original en detalles como la destrucción de gas. Echa un vistazo aquí: ethereum.stackexchange.com/questions/15166/…requireassertthrowthrow

Gran respuesta de Rob Hitchens . Me gustaría señalar un cambio de ruptura menor en el código mencionado. En newEntityfunción del simpleListcontrato, el autor ha utilizadoreturn entityStructs.push(newEntity)-1;

Desde la versión 0.6.0 de Solidity, la función array.push() no devuelve nada. Consulte la respuesta aquí: https://ethereum.stackexchange.com/a/87791/73743 . Entonces, la return entityStructs.push(newEntity)-1;parte del código rompe el contrato inteligente.

Eso es bastante correcto. Ocurren cambios importantes. Con suerte, los lectores comprenderán la esencia de lo que está sucediendo y sabrán cómo refactorizar o echar un vistazo a la biblioteca para obtener ejemplos más actualizados (¡2017!). array--ha sido reemplazado por .pop, por ejemplo.

¡Gracias Rob por la inspiración!

Aquí hay una versión extendida de Mapped Structs con Delete-enabled Index que también admite mapeos anidados, datos "no estructurados" y propiedad

Espero que ayude a alguien

Se desaconseja la respuesta de solo enlace. Es mejor incluir la idea principal en la respuesta y solo usar el enlace para ampliar los detalles.