Skip to content

Storage Types

Stuart Farmer edited this page Sep 5, 2018 · 5 revisions

Key Concepts

Prefixes

Every data type that stores and accesses data needs a prefix. This is similar to a table name in SQL based storage. The prefix allows each contract to silo different data seperately, but access it from a common interface if needed. It also prevents data from different contracts from interacting with each other unless the contract author explicitly allows for it.

Prefixes have to be unique strings. At this point in time, it's up to the user to manage this. We are investigating management solutions that are automatic so that the contract author does not need to worry about this.

Prefixes inside of a smart contract also leave out the true contract prefix namespacing. For example, a prefix of balances on the token contract made by user stu would actually have a true prefix of stu:token:balances rather than just balances. This allows us to isolate smart contracts pretty simply.

Pending Issues with Prefixes

It's possible at this moment to create a self-referencing data type by reusing a prefix in a data type that expects another Seneca storage type. Here's what is looks like:

m = hmap('balances', str, hmap(None, str, int))
m['stu'] = hmap('balances', str, int)
assert m['stu']['stu']['stu'] == hmap('balances', str, int)

We are developing ways to prevent this.

Hmap

Hmap (aka. hash map) is the standard storage datatype. It is analogous to a Python dictionary. You store data via keys and can access them later using those same keys. It can be used to make object-like data structures.

# hmap(prefix, key_type, value_type)

m = hmap('balances', str, int)
m['stu'] = 100
assert m['stu'] == 100

You can nest hmaps within each other to make interesting data structures. To do this, you do something like this:

m = hmap('balances', str, hmap(None, str, int))
m['stucoin']['carl'] = 1000000

Hlist

Hlist (aka. hash list) aims to mimic the behavior of a standard Python list. Whereas an hmap can only get and set, an hlist has the following methods available:

get(index)

Retrieves an item at a given index.

l = hlist('orders', int)
l.append(1)
l.append(2)
assert l.get(1) == 2
assert l[0] == 1
set(index, value)

Sets the value at a given index. The index must be in range.

l = hlist('orders', int)
l.append(1)
l.append(2)
l[0] = 100
assert l[0] == 100
push(value)

Adds a value at index 0 and pushes the rest of the values up an index. Equivalent to adding an element on the left side of the list.

l = hlist('orders', int)
l.push(1)
l.push(2)
assert l[0] == 2
pop()

Returns the element at index 0 and deletes it simultaneously from the list.

l = hlist('orders', int)
l.push(1)
l.push(2)
a = l.pop()
b = l.pop()
assert len(l) == 0
assert a == 2
assert b == 1
remove(index)

Deletes an entry at a specified index.

l = hlist('orders', int)
l.push(1)
l.push(2)
l.remove(0)
a = l.pop()
assert a == 1
append(value)

Equivalent to the Python list append method. Adds an element to the end of the list.

l = hlist('orders', int)
l.append(1)
l.append(2)
assert l[0] == 1
assert l[1] == 2
extend(list)

Equivalent to the Python list extend method. Loops through the provided list, appending every value to it to the end of referenced list.

l = hlist('orders', int)
l.extend([1, 2, 3, 4, 5])
assert l[4] == 5

Table

Table types map a key to a Python dictionary. This is different than an hmap because you are given a primary key to access rows on default. This method is also faster than using nested hmaps.

t = table('people', {'name': str, 'balance': int, 'transfers': int})
t.set('stu', {'name': 'stu', 'balance': 1000, 'transfers': 0})

You can also get and set values as a subset of the total schema.

stu_balance = t.get('stu', ('balance',))
assert stu_balance == 1000

As always, you can reference other storage types such as hmaps and hlists in tables, which makes it powerful for a wide range of applications.

t = table('order_book', int, {'total': int, 'orders': hlist(None, int)})
t.set(100, {'total': 0, 'orders':hlist('100', int)})
r = t.get(100)
t['orders'].push(1000)
t['total'] += 1000

Ranked

Ranked sets keeps a score for each value it stores which allows inline ranking of items. This is perfect for voting systems on the blockchain.

add(member, score)

Adds a value and assigns it a score.

r = ranked('votes')
r.add('stu', 1)
delete(member)

Deletes a member by a set. All members must be unique!

r = ranked('votes')
r.add('stu', 1)
r.delete('stu')
get_max()

Returns the highest scoring member.

r = ranked('votes')
r.add('stu', 1)
r.add('davis', 2)
assert r.get_max() == 'davis'
get_min()

Returns the lowest scoring member

r = ranked('votes')
r.add('stu', 1)
r.add('davis', 2)
assert r.get_min() == 'stu'
pop_max()

Returns the highest scoring member and removes it from the set.

r = ranked('votes')
r.add('stu', 1)
r.add('davis', 2)

s = r.pop_max()
assert s == 'stu'
assert r.get_max() == 'davis'
pop_min()

Returns the lowest scoring member and removes it from the set.

r = ranked('votes')
r.add('stu', 1)
r.add('davis', 2)

d = r.pop_min()
assert d == 'davis'
assert r.get_max() == 'stu'
score(member)

Returns the rank of a member in the set.

r = ranked('votes')
r.add('falcon', 155)

_s = r.score('falcon')

assert _s == 155
increment(member, i)

Increments the score of the member by i. If the member is not in the set, it adds it and increments the score by i starting at score 0.

r = ranked('votes')

r.add('falcon', 155)
r.increment('falcon', 100)

_s = r.score('falcon')

assert _s == 255

r.increment('raghu', 123)

_sr = r.score('raghu')

assert _sr == 123

Clone this wiki locally