4 min read

Architecting Ethereum Database for your DApp

Web3 DApp database resides in Ethereum state and designing this database is quite different from web2 database.

Ethereum is the popular blockchain out there, it has two parts - database for the storage and execution engine for smart contracts. Web2 developers are used to work with relational databases in tabular format but in web3 we need to build relational database from datatypes offered by the ecosystem.


To understand data structure we need to first understand Data types we can use with Solidity. Solidity has two types Value and Reference types.

Value Types

These type of variables are passed as a value meaning if some variable is passed as parameter to a function then instead of passing memory address of that variable actual value is passed to the function.

func(x); // Here if value of x is 50 then it's func(50)

Below are some types of Value Types:

  • Booleans - Boolean value types are represented as true or false and declared with keyword bool.
bool x = true;
bool y = false;
Bool Type in Solidity
  • Integers - Integers are of two types signed(int) and unsigned(uint) integers. Integers can start from 8 bits to 256 bits, can be defined as uint8 to uint256 and int8 to int256. Size is in multiples of 8 bits.
uint8 x;
uint16 y;
uint z; //represents uint256 by default
uint datatype in Solidity
  • Fixed point numbers - Same as integers fixed point numbers can be signed(fixed) or unsigned(ufixed). It can be declared by ufixedMxN or fixedMxN, where M(8 to 256 - divisible by 8) is number of bits and N is number of decimal points (0 to 80).
ufixed128x18 y; // same as ufixed
fixed z; // same as fixed128x18
Fixed point numbers in Solidity
  • Address - Contains or holds 20 bytes Ethereum wallet or contract address. Address can be defined with address keyword. Address can be of payable type which will have transfer and send functions.
address myAddress; //represents contract or user wallet address

address payable myPayableAddress; // payable address comes with extra mehods (send, transfer)
Address data type in Solidity
  • Fixed-size byte arrays - Byte arrays store bytes and comes with sizes ranging from 1 to 32 bytes. These arrays can be defined using keywords bytes1, bytes2, .. bytes32
bytes1 x; // byte is alias for bytes1 and can be used
bytes2 y;
bytes32 z ;
Bytes data type in Solidity
  • Enums - Enums are user defined types and can have multiple elements which represents some integer with each value specified in enum. It can be defined with keyword enum.
enum Status {ON, OFF}; //defining enum

Status x; // defining enum variable, will have first element as default value
Enum data type in Solidity

Reference Types

Reference types are nothing but address references for particular variable. While using reference types we need to define the storage(memory, storage, calldata).

  • Arrays - Array of any datatype are assigned or referred via memory where it is stored.
//Defining dynamic array
uint[] x;
Arrays in Solidity
  • Structs - Structs in solidity has different size so instead of copying it by value it is referred via address.
struct Client{
	uint client_id,
    string name
};

Client x;
Structs in Solidity

Once you get idea about all the datatypes in Ethereum you can proceed to create structure for your web3 application. For more info about Data types read it here.

Content Subscription System

Let's create a database for subscription based content creation system in web3 like OnlyFans. Here we will have different stakeholders like: Creator, Subscriber. We need following functionalities in our contract:

  • Creator can create his/her account
  • Creator can activate/deactivate own account
  • Creator can create & upload content(We will not cover it in this post)
  • Subscriber can subscribe to creators content with specified subscription fee by the creator

To store data in smart contract we can use state variables of above discussed types. First of all we need structure for storing creators information, we can create it using struct.

Creator

  • accountName - Creators account name. As it will be array of bytes we can store it as bytes or string type.
  • description - This will be description provided by creator for it to rank. This can be stored as bytes or string.
  • balance - Let's say there are 10 subscribers for the creator their fees would be stored in the contract and balance for each creator is stored in balance. Balance of any particular creator will be non-negative number, so we will use
  • subDays - Number of days subscription would be active for new subscribers. For a month of subscription 30 will be the value.
  • subFee - Fee charged for subscription of subDays.
  • active - If Creator account is activated or not.
  • check - This flag will be used for identifying if Creator exists in the array or not. By default for each wallet address check will return false, so while creating Creator we need to set it to true. And for Creators existence we can check this value.
// Content Creator Structure

struct Creator {
  string accountName;
  string desription;
  uint balance;
  uint subDays;
  uint subFee;
  bool active;
  bool check;
}

In web2 systems we should have created database table consisting of accountName, description, balance, subDays, subFee and active columns.

If you notice we have not stored address of creator in the struct, as it will not be useful to find particular creator and we may need to visit each and every struct to find the particular address.

Creator mapping

To overcome this we will define a mapping between address and Creator type which will store list of creators pointing to particular Creator struct.

// Key storage of creators array
// creatorList[uint] will point to element in creators
// e.g. 	0x55 => Creator{}
// 		0x66 => Creator{}
mapping(address => Creator) public creatorList;

We can create Creator like below and check if creator exists in the mapping or not.

// Creation of Creator
// Here we are creating Creator object and assigning reference
// to msg.sender in creatorList
creatorList[msg.sender] = Creator(name, desc, 0, subdays, fee, true, true);

//Check if creator exists
if(creatorList[addr1].check == true){
	//creator with addr1 exists as we passed true for check field
}

Subscriptions

Once creator is created subscribers(i.e. wallets) can subscribe to the Creator. As subscriber will be a wallet we do not need to store information for subscriber other than address.

We should store Subscription info with starting time and subscription end time. For that we need array of user defined struct.

// Subscription structure 
struct Subcscription {
   address creator;
   uint startDate;
   uint endDate;
}

// Subscription Storage
Subcscription[] public subscriptions;

Now imagine situation where one subscriber wants to subscribe to multiple creators, then we need some mapping between list of all the subscriptions and mapped it to subscribers address. As address list of subscriber should be unique and should point to multiple subscriptions.

Subscription list with address=>(creator1, creator2), So any address can subscribe to multiple creators
ex. addr1 => [cr1, cr2]
     addr2 => [cr2, cr3]

Here cr1, cr2, cr3 are indexes of subscriptions array (Subscription[])

mapping(address => uint[]) public subList;

Now let's see how we can subscribe to a Creator using above data structures:

// We need to add element to subscriptions array with start and end time
subscriptions.push(Subcscription(creator, block.timestamp, _end));

// assign index of created Subscsription to the subList of subscriber i.e. msg.sender
subList[msg.sender].push(subscriptions.length - 1);

Conclusion

We need to articulate web3 smart contract data structure in similar way as we handle in web2 but the relations between different data structures needs to be handled explicitly.

References: