Eliminación del índice de una matriz en términos de optimización para bucles

Tengo un dapp donde un administrador puede cargar una cantidad de pasteles (20 a 30). Las personas pueden ingresar al sitio y decidir si quieren comprar un pastel cargado por el administrador.

Entonces, para el contrato inteligente, hice una matriz para uintalmacenar cada uno cada vez que el administrador carga. Una vez que el comprador compró uno de los pasteles, eliminaría el de la matriz. Decidí eliminarlo para evitar bucles en la medida de lo posible, ya que se quedaría sin gasolina mientras iteraba la matriz para devolver la lista restante de pasteles a los compradores. ¿Cómo suena en términos de optimización?idCakeidCake

A continuación se muestra mi implementación. ¿Hay mejores soluciones o es esto suficiente para continuar?

struct Cake {
    uint id;
    address buyer;
}

mapping (uint => Cake) public cakes;
uint[] public cakeIds;

function addNewCake(uint _id) public {
    cakes[_id] = Cake(_id, 0x0);

    cakeIds.push(_id);
}

function buyCake(uint _id) public {      
    Cake storage cake = cakes[_id];       
    cake.buyer = msg.sender;

    removeCakeInArray(_id);
}

function removeCakeInArray(uint _id) private {
    for (uint i = 0; i <= getNumOfCakes(); i++) {            
        if (cakeIds[i] == _id) {
            remove(i);                 
        }    
    }
}

function remove(uint index) private {
    if (index >= getNumOfCakes()) return;

    for (uint i = index; i < getNumOfCakes() - 1; i++){
        cakeIds[i] = cakeIds[i+1];
    }
    cakeIds.length--;
}

function getAllUnsoldCakes() public view returns (uint[]) {
    uint length = getNumOfCakes();  
    uint[] memory ids = new uint[](length);

    for (uint i = 0; i < length; i++) {       
        uint cakeId = cakeIds[i];
        Cake memory cake = cakes[cakeId];

        if (cake.buyer == 0x0) {
            ids[i] = cake.id;             
        }
    }

    return ids;
}

function getNumOfCakes() public view returns (uint) {
    return cakeIds.length;
}

Respuestas (2)

Estos bucles, particularmente en remove(), son una señal de alerta: su conjunto puede crecer hasta una longitud ilimitada, incluso si alguien lo aumenta maliciosamente, en el que su contrato alcanzará los límites de gas y quedará inutilizable. Esto solo está bien si puede poner límites explícitos en la longitud de la lista; por ejemplo, si alguien intenta agregar un undécimo pastel sin vender, revertla transacción, porque los estantes de la pastelería están llenos.

Si realmente necesita poder recuperar una lista de pasteles no vendidos con una llamada de función de solidez, un enfoque más seguro es usar una lista vinculada, ya que se puede actualizar por el mismo costo independientemente de la longitud de la lista. Esto todavía le da un costo de gasolina del que preocuparse cuando lee la lista, pero eso puede estar bien si solo se va a usar para leer datos de un nodo, en lugar de llamarlo como parte de una transacción.

Sin embargo, ¿realmente necesita poder obtener una lista de pasteles no vendidos con una llamada de función de contrato? Puede ser mejor transferir ese trabajo a otro lugar, por ejemplo, registrar eventos cuando agrega o vende un pastel, luego lea esos eventos desde su dapp javascript y utilícelos para crear la lista que muestra al usuario.

Gracias por tus comentarios. No estoy seguro de por qué remove()podría ser una bandera roja. ¿Cuáles son los escenarios en los que alguien puede aumentar una matriz a propósito?
Supongo que si addNewCake()está restringido al administrador (que en su código no lo es) y son completamente confiables y todo el contrato se rompe de todos modos, su clave es robada, entonces está bien. Si alguna de estas cosas no funciona, un usuario malintencionado puede simplemente llamar addNewCake()hasta que la matriz sea lo suficientemente larga como para que el bucle remove()use más gas que el límite de gas del bloque.
Tiene sentido. Además, para llamar a los pasteles no vendidos, estoy pensando en usar archivos .json en primer lugar para mostrar todos los pasteles y marcarlos como vendidos si un comprador vende el pastel (simplemente almacene la dirección del comprador y la identificación del pastel en el cadena de bloques). ¿Esto lo haría más suave?
Vendido o no afecta si la transacción se realiza, por lo que probablemente haría esa parte en la cadena de bloques, pero poner cualquier detalle legible por humanos (nombre del pastel, etc.) en archivos json en ipfs y almacenar la dirección ipfs en la cadena de bloques bien puede hacer sentido.

Secundando las preocupaciones de Edmond sobre los bucles. En mi opinión , la necesidad de enumerar la lista es cuestionable. El contrato podría funcionar solo como anexo con una bandera simple para indicar "vendido" y eventos que adiciones y eliminaciones crónicas. Entonces, los clientes podrían calcular por sí mismos qué pasteles quedan disponibles para la venta y el contrato simplemente haría cumplir la regla de que ningún pastel puede venderse dos veces. De manera similar, las listas de pedidos generalmente pueden ser externas a los contratos de almacenamiento.

En el caso de que otro contrato pueda estar interesado en navegar con éxito por la lista, es posible que deba hacer que esta información se pueda descubrir de manera eficiente sin depender de los eventos. Esto incluiría la preocupación de "eliminar".

En ese caso, considere la Estructura asignada con el patrón Eliminar que se describe aquí: ¿Existen patrones de almacenamiento simples y bien resueltos para Solidity? con una descripción elaborada aquí: https://medium.com/@robhitchens/solidity-crud-part-1-824ffa69509a

Espero eso ayude.