Skip to content

Storage

Contracting stores state in a key-value storage system where each smart contract has it's own data space that cannot be accessed by other smart contracts besides through the @export functions.

When you submit a smart contract, keys are created to store the code and compiled bytecode of the contract. For example:

python
owner = Variable()

@construct
def seed():
    owner.set(ctx.caller)

When submitted will create the following in state space:

KeyValue
contract.__compiled__Python Bytecode
contract.__code__Python Code
contract.ownerctx.caller at submission time

Storage follows a simple pattern such that each variable or hash stored is prefaced by the contract name and a period delimiter. If the variable has additional keys, they are appended to the end seperated by colons.

<contract_name>.<variable_name>
<contract_name>.<variable_name>:<key_0>:<key_1>:<key_2>...

Encoding

Data is encoded as JSON in the state space. This means that you can store simple Python objects such as dictionaries, arrays, tuples, and even Datetime and Timedelta types (explained later.)

python
player = Variable()
stats = {
	'name': 'Steve',
	'level': 100,
	'type': 'Mage',
	'health': 1000
}
player.set(stats)

steve = player.get()

steve['health'] -= 100
steve['health'] == 900 # True
python
authorized_parties = Variable()
parties = ['steve', 'alex', 'bill', 'raghu', 'tejas']
authorized_parties.set(parties)

# This will fail if the contract sender isn't in the authorized parties list.
assert ctx.caller in authorized_parties.get()

Storage Types

There are two types of storage: Variable and Hash. Variable only has a single storage slot. Hash allows for a dynamic amount of dimensions to be added to them. Hashes are great for data types such as balances or mappings.

python
owner = Variable()
balances = Hash()

@export
def example():
    owner.set('hello')
    a = owner.get()

    balances['bill'] = 100
    a = balances['something']

Variable API

python
class Variable(Datum):
    def __init__(self, contract, name, driver: ContractDriver=driver, t=None):
        ...

    def set(self, value):
        ...

    def get(self):
        ...

__init__(self, contract, name, driver, t)

The __init__ arguments are automatically filled in for you during compilation and runtime. You do not have to provide any of them.

some_contract.py (Smart Contract)

python
owner = Variable()

This translates into:

python
owner = Variable(contract='some_contract', name='owner')

Driver is pulled from the Runtime (rt) module when the contract is being executed. If you provide a type to t, the Variable object will make sure that whatever is being passed into set is the correct type.

set(self, value)

some_contract.py (Smart Contract)

python
owner = Variable()
owner.set('bill')

Executes on contract runtime and sets the value for this variable. The above code causes the following key/value pair to be written into the state.

KeyValue
some_contract.ownerbill

NOTE: You have to use the set method to alter data. If you use standard =, it will just cause the object to be set to whatever you pass.

python
owner = Variable()
owner
>> <Variable at 0x10577cda0>

owner = 5
owner
>> 5

get(self)

some_contract.py (Smart Contract)

python
owner = Variable()
owner.set('bill')

owner.get() == 'bill' # True

Returns the value that is stored at this Variable's state location.

NOTE: The converse applies to the get function. Simply setting a variable to the Variable object will just copy the reference, not the underlying data.

python
owner = Variable()
owner.set('bill')
owner.get()
>> 'bill'

a = owner
a
>> <Variable at 0x10577cda0>

Hash API

python
class Hash(Datum):
    def __init__(self, contract, name, driver: ContractDriver=driver, default_value=None):
        ...

    def set(self, key, value):
        ...

    def get(self, item):
        ...

    def all(self, *args):
        ...

    def clear(self, *args):
       ...

    def __setitem__(self, key, value):
        ...

    def __getitem__(self, key):
        ...

__init__(self, contract, name, driver, default_value)

Similar to Variable's __init__ except that a different keyword argument default_value allows you to set a value to return when the key does not exist. This is good for ledgers or applications where you need to have a base value.

some_contract.py (Smart Contract)

python
balances = Hash(default_value=0)
balances['bill'] = 1_000_000

balances['bill'] == 1_000_000 # True
balances['raghu'] == 0 # True

set(self, key, value)

Equivalent to Variable's get but accepts an additional argument to specify the key. For example, the following code executed would result in the following state space.

some_contract.py (Smart Contract)

python
balances = Hash(default_value=0)
balances.set('bill', 1_000_000)
balances.set('raghu', 100)
balances.set('tejas', 777)
KeyValue
some_contract.balances:bill1,000,000
some_contract.balances:raghu100
some_contract.balances:tejas777

Multihashes

You can provide an arbitrary number of keys (up to 16) to set and it will react accordingly, writing data to the dimension of keys that you provided. For example:

subaccounts.py (Smart Contract)

python
balances = Hash(default_value=0)
balances.set('bill', 1_000_000)
balances.set(('bill', 'raghu'), 1_000)
balances.set(('raghu', 'bill'), 555)
balances.set(('bill', 'raghu', 'tejas'), 777)

This will create the following state space:

KeyValue
subaccounts.balances:bill1,000,000
subaccounts.balances:bill:raghu1,000
subaccounts.balances:raghu:bill555
subaccounts.balances:bill:raghu:tejas777

get(self, key)

Inverse of set, where the value for a provided key is returned. If it is None, it will set it to the default_value provided on initialization.

some_contract.py (Smart Contract)

python
balances = Hash(default_value=0)
balances.set('bill', 1_000_000)
balances.set('raghu', 100)
balances.set('tejas', 777)

balances.get('bill') == 1_000_000 # True
balances.get('raghu') == 100 # True
balances.get('tejas') == 777 # True

The same caveat applies here

Multihashes

Just like set, you retrieve data stored in multihashes by providing the list of keys used to write data to that location. Just like get with a single key, the default value will be returned if no value at the storage location is found.

subaccounts.py (Smart Contract)

python
balances = Hash(default_value=0)
balances.set('bill', 1_000_000)
balances.set(('bill', 'raghu'), 1_000)
balances.set(('raghu', 'bill'), 555)
balances.set(('bill', 'raghu', 'tejas'), 777)

balances.get('bill') == 1_000_000 # True
balances.get(('bill', 'raghu')) == 1_000 # True
balances.get(('raghu', 'bill')) == 555 # True
balances.get(('bill', 'raghu', 'tejas')) == 777 # True

balances.get(('bill', 'raghu', 'tejas', 'steve')) == 0 # True

NOTE: If storage returns a Python object or dictionary, modifications onto that dictionary will not be synced to storage until you set the key to the altered value again. This is vitally important.

python
owner = Hash(default_value=0)
owner.set('bill') = {
	'complex': 123,
	'object': 567
}

d = owner.get('bill') # Get the dictionary from storage
d['complex'] = 999 # Set a value on the retrieved dictionary
e = owner.get('bill') # Retrieve the same value for comparison

d['complex'] == e['complex'] # False
python
owner = Hash(default_value=0)
owner.set('bill') = {
	'complex': 123,
	'object': 567
}

d = owner.get('bill') # Get the dictionary from storage
d['complex'] = 999 # Set a value on the retrieved dictionary

owner.set('bill', d) # Set storage location to the modified dictionary

e = owner.get('bill') # Retrieve the same value for comparison

d['complex'] == e['complex'] # True!

__setitem__(self, key, value):

Equal functionality to set, but allows slice notation for convenience. This is less verbose and the preferred method of setting storage on a Hash.

subaccounts.py (Smart Contract)

python
balances = Hash(default_value=0)
balances['bill'] = 1_000_000
balances['bill', 'raghu'] = 1_000
balances['raghu', 'bill'] = 555
balances['bill', 'raghu', 'tejas'] = 777

NOTE: The problem that occurs with Variable's set does not occur with Hashes.

python
owner = Hash(default_value=0)
owner['bill'] = 100
owner['bill']
>> 100

__getitem__(self, key):

Equal functionality to set, but allows slice notation for convenience. This is less verbose and the preferred method of setting storage on a Hash.

subaccounts.py (Smart Contract)

python
balances = Hash(default_value=0)
balances['bill'] = 1_000_000
balances['bill', 'raghu'] = 1_000
balances['raghu', 'bill'] = 555
balances['bill', 'raghu', 'tejas'] = 777

balances['bill'] == 1_000_000 # True
balances['bill', 'raghu'] == 1_000 # True
balances['raghu', 'bill'] == 555 # True
balances['bill', 'raghu', 'tejas'] == 777 # True

balances['bill', 'raghu', 'tejas', 'steve'] == 0 # True

all(self, *args):

Returns all of the values in a particular hash. For multihashes, it returns all values in that 'subset' of hashes. Assume the following state space:

KeyValue
subaccounts.balances:bill1,000,000
subaccounts.balances:bill:raghu1,000
subaccounts.balances:bill:tejas555
subaccounts.balances:raghu777
subaccounts.balances:raghu:bill10,000
subaccounts.balances:raghu:tejas100,000
python
balances.all()
>> [1000000, 1000, 555, 777, 10000, 100000]

balances.all('raghu')
>> [777, 10000, 100000]

clear(self, *args)

Clears an entire hash or a section of a hash if the list of keys are provided. Assume the same state space:

KeyValue
subaccounts.balances:bill1,000,000
subaccounts.balances:bill:raghu1,000
subaccounts.balances:bill:tejas555
subaccounts.balances:raghu777
subaccounts.balances:raghu:bill10,000
subaccounts.balances:raghu:tejas100,000
python
balances.clear('bill')
balances.all() # None of Raghu's accounts are affected
>> [777, 10000, 100000]
python
balances.clear()
balances.all() # All entries have been deleted
>> []