Table Indexing

When it comes to indexing your tables, you have to be mindful of which data you'll need to access quickly/frequently within your contract. Less frequently accessed data can be handed using an off-chain indexer, or even a simple API call if you aren't storing tons of rows in your tables.

There are several options here, I'll outline the most common approaches below.

available_primary_key

This is the most straightforward of all. This method will just use the next available key, based on the highest value currently stored in your table. For example...

struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] balances {
	uint64_t 	id;
	eosio::name  	wallet;
	eosio::asset 	balance;
  
  uint64_t primary_key() const { return id; }
};
using balances_table = eosio::multi_index< "balances"_n, balances >;

ACTION createrow( const name& user, const asset& balance ){
	balances_table bal_t = balances_table( _self, _self.value );
	bal_t.emplace( user, [&](auto &_u){
		_u.id = bal_t.available_primary_key();
		_u.wallet = user;
		_u.balance = balance;
	});
}

By User

Let's say you have a table, and you are 100% certain that each user in the table will only need 1 row. You can handle this simply by using the user.value

struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] balances {
	eosio::name  	wallet;
	eosio::asset 	balance;
  
  uint64_t primary_key() const { return wallet.value; }
};
using balances_table = eosio::multi_index< "balances"_n, balances >;

ACTION someaction( const name& user ){
	require_auth( user );
	
	balances_table bal_t = balances_table( _self, _self.value );
	auto itr = bal_t.require_find( user.value, "you do not have a balance" );
}

By uint128_t Of 2 Values

Let's say for example, you have an NFT market contract. All of the drop purchases are stored in a single table, under the same scope. Each drop can contain multiple orders, and each user can have orders for multiple drops. Using the drop ID, or the user's wallet as a primary index will not work in this case. You can solve this using the following method.

#define mix64to128(a, b) (uint128_t(a) << 64 | uint128_t(b))

struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] orders {

	uint64_t  	order_id;
	eosio::name 	wallet;
	uint64_t  	drop_id;			

	uint64_t primary_key() const { return order_id; }
	uint128_t secondary_key() const { return mix64to128( wallet.value, drop_id ); }
};
using orders_table = eosio::multi_index<"orders"_n, orders,
eosio::indexed_by<"userdropmix"_n, eosio::const_mem_fun<orders, uint128_t, &orders::secondary_key>>
>;

ACTION findorder( const name& wallet, const uint64_t& drop_id ){
	orders_table orders_t = orders_table( _self, _self.value );
	auto orders_secondary = orders_t.get_index<"userdropmix"_n>();
	
	uint128_t user_drop_key = mix64to128( wallet.value, drop_id );
	auto itr = orders_secondary.require_find( user_drop_key, "no match found for this user + drop" );
}

By checksum256

Sometimes 128 bits is not enough to meet the needs for an index. For example, you may have a contract where you need to index by a combination of user, token and contract and be able to easily find that key. You can accomplish this by using a checksum256 hash, as demonstrated below.

struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] tokens {
	uint64_t  					id; 	// available_primary_key
	eosio::name  				user;
	eosio::extended_asset  		balance;

	uint64_t primary_key() const { return id; }

	// combine 3 values into a checksum256
	static eosio::checksum256 create_checksum(const uint64_t& a, const uint64_t& b, const uint64_t& c) {
		std::vector<char> data_stream(sizeof(uint64_t) * 3);
		eosio::datastream<char*> ds(data_stream.data(), data_stream.size());
		ds << a << b << c;

		auto result_hash = eosio::sha256(data_stream.data(), data_stream.size());
		return result_hash;
	}

	eosio::checksum256 by_user_token() const { return create_checksum( user.value, balance.quantity.symbol.code().raw(), balance.contract.value ); }

};
using tokens_table = eosio::multi_index<"tokens"_n, tokens,
      eosio::indexed_by<"userandtoken"_n, eosio::const_mem_fun<tokens, eosio::checksum256, &tokens::by_user_token>>
      >;

ACTION mycontract::dosomething(const name& user, const asset& quantity, const name& contract){
	tokens_table tokens_t = tokens_table( _self, _self.value );
	auto user_token_idx = tokens_t.get_index<"userandtoken"_n>();
	checksum256 user_token_key = tokens::create_checksum( user.value, quantity.symbol.code().raw(), contract.value );
	auto itr = user_token_idx.require_find( user_token_key, "could not locate user + token row" );
}

Last updated