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 uint
almacenar 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?id
Cake
id
Cake
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;
}
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, revert
la 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.
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.
bbusdriver
remove()
podría ser una bandera roja. ¿Cuáles son los escenarios en los que alguien puede aumentar una matriz a propósito?Edmundo Edgar
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 llamaraddNewCake()
hasta que la matriz sea lo suficientemente larga como para que el bucleremove()
use más gas que el límite de gas del bloque.bbusdriver
Edmundo Edgar