Para cada usuario, quiero mantener una matriz de activos retenidos (cada activo tiene una identificación).
Mi solución hasta ahora es:
struct User {
uint userId;
uint[] assets;
}
Por cada retención asset
del usuario, empujo a la matriz del usuario la ID
de asset
.
Quiero darle al usuario la opción de eliminar un archivo asset
.
¿Cuál sería el enfoque más eficiente para esto?
Asignar todo lo disponible assets
para cada usuario (sería un desperdicio dado que tiene muchos activos disponibles) VS. iterando sobre todos sus assets
cada vez que le gustaría eliminar un asset
, encontrándolo en la matriz, luego eliminándolo y cambiando toda la matriz en consecuencia; además, el código para esto es un poco horrible:
function deleteAsset(uint assetId) external returns(uint) {
bool assetFound = false;
// Check if the asset found is the last asset (or we go out of boundaries)
if (allUsers[msg.sender].assets[allUsers[msg.sender].assets.length - 1] == assetId){
assetFound = true;
}
else{
// Iterate over all user assets and find its index
for (uint i = 0; i < allUsers[msg.sender].assets.length - 1; i++) {
if (!assetFound && allUsers[msg.sender].assets[i] == assetId)
assetFound = true;
if(assetFound)
allUsers[msg.sender].assets[i] = allUsers[msg.sender].assets[i + 1];
}
}
if (assetFound){
delete allUsers[msg.sender].assets[allUsers[msg.sender].assets.length - 1];
allUsers[msg.sender].assets.length--;
}
}
Sería mucho más fácil si pudiera guardar un mapping
para cada usuario que indique qué activo tiene, pero no puede devolver un mapping
de una función y no conozco los puntos de referencia de las view
funciones y "fuerza bruta" todos los activos disponible para cada usuario puede llevar mucho tiempo, supongo.
Fuente: https://github.com/su-squares/ethereum-contract/blob/master/contracts/SuNFT.sol
Aqui tienes:
Algoritmo:
uint[] assets;
mapping(uint=>uint) indexOfAsset;
function removeAssetFromArray(uint _assetToDelete) {
uint index = indexOfAsset[_assetToDelete];
if (!index) return;
if (assets.length > 1) {
assets[index] = assets[assets.length-1];
}
assets.length--; // Implicitly recovers gas from last element storage
}
Advertencia: este enfoque asume un conjunto ordenado, no una matriz. En otras palabras, asume que la matriz no tiene elementos duplicados.
indexOfAsset
depende de su caso de uso. Para mi caso de uso (NFT que nunca se destruyen), a removeAssetFromArray
siempre va seguido de a addAssetToArray
y, por lo tanto, no es necesario eliminarlo de indexOfAsset
.Generalmente, moviendo el último elemento de la lista a la fila para eliminar.
Trabajando desde el Usuario struct
en el código del OP.
pragma solidity 0.5.1;
contract DeleteUser {
struct UserStruct {
uint userId;
uint[] assets;
}
mapping(address => UserStruct) public userStructs;
function deleteUserAsset(address user, uint assetIndex, uint asset) public {
UserStruct storage u = userStructs[user];
require(u.assets.length > assetIndex);
require(u.assets[assetIndex] == asset); // this is a sanity check in case the list was re-ordered
u.assets[assetIndex] = u.assets[u.assets.length-1];
u.assets.length--;
}
}
La verificación de cordura no es estrictamente necesaria ya que la pregunta está redactada, pero la función depende de que la persona que llama sepa qué fila eliminar. Esto podría ser un problema real si la lista se reorganiza por la deleteUserAsset()
función que se llama. Una solución simple es agregar ese cheque, para estar seguro.
Aún mejor, en mi opinión , es no confiar en que la persona que llama sepa esto. Para hacerlo, necesitaríamos una búsqueda de un solo paso para encontrar la fila en la que se encuentra una clave en particular. Se necesitará otra palabra de 32 bytes para almacenar eso.
pragma solidity 0.5.1;
contract DeleteUser {
struct UserStruct {
bytes32[] assets;
mapping(bytes32 => uint) assetPointers;
}
mapping(address => UserStruct) userStructs;
function isUserAsset(address user, bytes32 assetId) public view returns(bool isIndeed) {
if(userStructs[user].assets.length == 0) return false;
return userStructs[user].assets[userStructs[user].assetPointers[assetId]] == assetId;
}
function deleteUserAsset(address user, bytes32 assetId) public {
UserStruct storage u = userStructs[user];
require(isUserAsset(user, assetId));
uint rowToDelete = u.assetPointers[assetId];
u.assets[rowToDelete] = u.assets[u.assets.length-1];
u.assets.length--;
}
}
Espero eso ayude.
Para empezar, no pude crear una matriz de almacenamiento con más de 100 elementos en un constructor. Del mismo modo, supongo que tener que pasar por encima de uno también fallará cuando crezca demasiado.
Los resultados son claros, mover índices después de eliminar un elemento de una matriz dinámica es una idea horrible y los valores 0 deberían filtrarse en el cliente.
Recorriendo una matriz de almacenamiento con 10 entradas y eliminando el ÚLTIMO elemento
transaction cost 20514 gas
Recorriendo una matriz de almacenamiento con 100 entradas y eliminando el ÚLTIMO elemento
transaction cost 43349 gas
Recorriendo una matriz de almacenamiento con 10 entradas y eliminando el elemento MEDIO
transaction cost 44762 gas
Recorriendo una matriz de almacenamiento con 100 entradas y eliminando el elemento MEDIO
transaction cost 340387 gas
Ese aumento en el costo del gas simplemente no es justificable.
pragma solidity ^0.5.1; contract S {uint[] a;constructor() public {a.length = 2**20;}}
Karan Kurbur
ismael
Aleatorio
Román Frólov
Aleatorio