Digital Developers’ Fund

DDF’s Splitter Contract

Listomania

Dave Appleton
5 min readAug 7, 2017

--

A while back, my friend Tobias Ratschiller (author of phpMyAdmin) came to me with a problem.

Tobias had promised to help some friends of his, guys who were already running an investment fund called the Digital Developer’s Fund, get their smart contracts in place for their ICO. This fund promised to share a certain percentage of the fund’s profits with token holder in proportion to their asset holdings. They already knew, more or less, the crowdsale contract that they wanted to model on but the tricky bit was the allocation of the dividends.

Specification:
At a particular time, we want to send a specific bonus to each investor.
The bonus would be pro rata to the size of their actual investment.

As long as I have a pot of coffee nearby I am willing to take on any challenge so we discussed the issues and set up some criteria.

  • We did not want a pull contract. Investors could leave funds in for a long time without bothering to withdraw them. Contracts holding ether are targets.
  • We did not want a push contract (sends the dividends) because the gas calculations would be tough and there would be possibility of esoteric attacks if sending to a contract.

Er. OK. We don’t want to send the ether, nor do we want them to come and get it. What the heck do we want?

Maxim Number TWO*

My second blockchain maxim says that we should only do as much ON the blockchain as we really have to. Everything else should happen off chain.

The obvious thought would be to use a block explorer to work out each person’s holdings and divide that by the total allocation of that token.

Unfortunately we needed these balances at a particular moment in time. We could not afford to have a race condition where somebody updates their records halfway through the disbursal process. While it may have been possible to get an accurate snapshot of the list, it would be hard to prove it to be definitive so the obvious (to me at least) solution was to have a hybrid solution.

I proposed that we should deploy what I termed a “splitter” contract. A contract that would record and tell us how the dividends should be divided but let us do the distribution of those funds off line using a script that uses data from the list derived by reading that contract.

In order to keep a record of who is holding DDF tokens, the splitter contract employs a doubly linked list to record who is still holding tokens. This list is updated every time tokens are transferred.

The basic data record was as follows:

struct xRec {
bool inList;
address next;
address prev;
uint256 val;
}
address public first;
address public last;
mapping (address => xRec) public theList;

first and last start off pointing to the initial genesis record which holds the reserve funds. You can see this in the constructor:

 function splitterContract(address seed, uint256 seedVal) {
first = seed;
last = seed;
theList[seed] = xRec(true,0x0,0x0,seedVal);
}

Then every time we want to add a new record to the list, we add it to the tail of the list.

function add(address whom, uint256 value) internal {
theList[whom] = xRec(true,0x0,last,value);
theList[last].next = whom;
last = whom;
ev("add",whom,value);
}

removal of a record is almost as simple bearing in mind that there must always be at least ONE token holder. We would only remove a record if it had no value.

function remove(address whom) internal {
if (first == whom) {
first = theList[whom].next;
theList[whom] = xRec(false,0x0,0x0,0);
return;
}
address next = theList[whom].next;
address prev = theList[whom].prev;
if (prev != 0x0) {
theList[prev].next = next;
}
if (next != 0x0) {
theList[next].prev = prev;
}
theList[whom] = xRec(false,0x0,0x0,0);
ev("remove",whom,0);
}

Somebody will ask: how can prev be equal to zero if we have already established that it is not first on the list?
Well — I’m a firm believer in belt and braces but yeah — maybe not necessary.

All that is needed now is to call these two functions and we can maintain a linked list of token holders’ addresses. Every time a token is transferred it calls this function in the splitter

function update(address whom, uint256 value) onlyMeOrDDF {
if (value != 0) {
if (!theList[whom].inList) {
add(whom,value);
} else {
theList[whom].val = value;
ev("update",whom,value);
}
return;
}
if (theList[whom].inList) {
remove(whom);
}
}

Thus we can walk through the list from an external program that can read the contract. It starts with theList[first] and walks along the list by moving to theList[thisRec].next until it next points to nil.

PROBLEM RACE HAZARD AHEAD

We still have not solved the problem of a race hazard — what happens if somebody updates the list while we are walking through it…

This was solved by having a thinkmode variable.
Before starting the walk the list, thinkMode would be set TRUE.

function startThinking() onlyOwner {
thinkMode = true;
pos = 0;
}

When in thinkMode, the update function would shunt updates into a queue for processing later. Add a few lines to update

function update(address whom, uint256 value) onlyMeOrDDF {
if (thinkMode) {
addRec4L8R(whom,value);
return;
}

Add the function addRec4L8R to hold the queue

struct l8r {
address whom;
uint256 val;
}
l8r[] afterParty;function addRec4L8R(address whom, uint256 val) internal {
afterParty.push(l8r(whom,val));
}

Once the process was complete, queued items would be reapplied to the list until the queue was fully processed. We may not be able to process the entire list at once due to gas usage — so we process num records in one go using pos as the next record to be processed.

function stopThinking(uint256 num) onlyOwner {
thinkMode = false;
for (uint256 i = 0; i < num; i++) {
if (pos >= afterParty.length) {
delete afterParty;
return;
}
update(afterParty[pos].whom,afterParty[pos].val);
pos++;
}
thinkMode = true;
}

Obviously the update function needs to be called by the owner (via stopThinking) and by the DDF contract.

Conclusion

With startThinking and stopThinking, it becomes safe to walk the list without fear of the list getting corrupted.

*Maxim Number One

BlockChain will become a standard tool in every competent programmer’s toolkit. Just like database and RESTful APIs.

Notes

You can see the DDF token contract and the DDF splitter contract on etherscan.io.

--

--

Dave Appleton

HelloGold's blockchain lead and Senior Advisor at Akomba Labs; a technology anachronism who codes, teaches, mentors and consumes far too much caffeine.