Simple and appropriate data organization can challenge Solidity newcomers. It wants us to organize everything in ways many of us aren’t accustomed to.
Are there well-solved general patterns for routine on-chain data organization?
Simple and appropriate data organization can challenge Solidity newcomers. It wants us to organize everything in ways many of us aren’t accustomed to.
Are there well-solved general patterns for routine on-chain data organization?
Here are some simple and useful patterns in increasing order of utility.
Event logs are omitted for brevity. In practice, it's desirable to emit events for every important state change.
Simple List Using Array
Strengths
Weaknesses
Example:
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;
}
}
Mapping with Struct
Strengths
Weaknesses
Example:
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)) throw;
entityStructs[entityAddress].entityData = entityData;
entityStructs[entityAddress].isEntity = true;
return true;
}
function deleteEntity(address entityAddress) public returns(bool success) {
if(!isEntity(entityAddress)) throw;
entityStructs[entityAddress].isEntity = false;
return true;
}
function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
if(!isEntity(entityAddress)) throw;
entityStructs[entityAddress].entityData = entityData;
return true;
}
}
Array of Structs with Unique Ids
Strengths
Weaknesses
Example:
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)) throw;
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)) throw;
if(entityStructs[rowNumber].entityAddress != entityAddress) throw;
entityStructs[rowNumber].entityData = entityData;
return true;
}
}
Mapped Structs with Index
Strengths
Weaknesses
Example:
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)) throw;
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)) throw;
entityStructs[entityAddress].entityData = entityData;
return true;
}
}
Mapped Structs with Delete-enabled Index
Strengths
Weaknesses
UPDATE, 2019
This pattern is available as a library for Solidity 0.5.1: https://medium.com/@robhitchens/solidity-crud-epilogue-e563e794fde, https://github.com/rob-Hitchens/UnorderedKeySet
Example:
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)) throw;
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)) throw;
entityStructs[entityAddress].entityData = entityData;
return true;
}
function deleteEntity(address entityAddress) public returns(bool success) {
if(!isEntity(entityAddress)) throw;
uint rowToDelete = entityStructs[entityAddress].listPointer;
address keyToMove = entityList[entityList.length-1];
entityList[rowToDelete] = keyToMove;
entityStructs[keyToMove].listPointer = rowToDelete;
entityList.length--;
return true;
}
}
This last one has an explainer here: https://medium.com/@robhitchens/solidity-crud-part-2-ed8d8b4f74ec#.ekc22r5lf
Folder Tree Example: How can we organize storage of a folder or object tree in Solidity?
Linked List example shows a way to maintain an ordered list using a library. https://github.com/ethereum/dapp-bin/blob/master/library/linkedList.sol 0
keyExists()
to prevent confusion.
– Rob Hitchens - B9lab
May 20 '17 at 12:25
inactive
or usedUp
. You could end up with clients that have to iterate over an ever-increasing list of obsolete information. Sometimes (not majority) it makes sense to truly remove something from the list. The delete-enabled pattern solves for that.
– Rob Hitchens - B9lab
Apr 18 '18 at 16:37
Adding to Rob's answer, please use revert() as an alternative of throw.From version 0.4.13 the throw keyword is deprecated and will be phased out in the future. Read here for more information: require, assert and revert in solidity.
So, as an example you should change
if(isEntity(entityAddress)) throw;
to
if(isEntity(entityAddress)) revert();
in above code provided by Rob.
revert()
: ethfiddle.com/PgDM-drAc9
– James Duffy
Nov 14 '17 at 6:39
require()
but that would mean reversing all the rules ... if(bad) revert()
=> require(!bad)
– Rob Hitchens - B9lab
Nov 14 '17 at 14:48
throw
and revert()
? Do they do the same thing, by reverting back all the states? @Abhishek Sinha && @Rob Hitchens
– alper
Dec 9 '17 at 14:55
solc 0.4.13
, revert
, require
and assert
deprecate throw
. They differ slightly from the original throw
in the details such as gas destruction. Have a look over here: ethereum.stackexchange.com/questions/15166/…
– Rob Hitchens - B9lab
Dec 9 '17 at 16:03