Solidity and Prime Numbers

A thought experiment in reducing parameters passed through the constructor of an Ethereum smart contract.

by John Quarnstrom


Let’s imagine we are writing a “factory” smart contract — one that will act as a blueprint for issuing hundreds of other, unique contracts. We intend to manage payment preferences through these contracts, as they will accept one or more Stablecoins as “payment” for the contract’s specific use-case.

Challenge: Users have different preferences — some will accept DAI or Tether, others will accept TrueUSD and DAI, and some will only accept Basis.

How can we manage all of these preferences in a seamless way through the constructor function? For this example, let’s assume there are five different Stablecoins that merchants/traders/whoever want to accept.


Option #1 (Lame) — Pass through five different boolean parameters in the construction function and assign them to individual boolean variables:

constructor (bool _dai, bool _trueusd, bool _tether, ... ) {
    DAI_Accepted = _dai;
    TrueUSD_Accepted = _trueusd;
   Tether_Accepted = _tether;
}

This option is acceptable. We can use modifiers to check whether the state variables (e.g. DAI_Accepted) are true or not when accepting payments.

Is there an easier way, that doesn’t require passing through 5+ parameters?
Yes.


Option #2 (Cool) — Pass through one uint that is a multiple of prime numbers corresponding to a truth table.

Let’s imagine we have five different stablecoins:
DAI, TrueUSD, Tether, Basis, and Haven (Havven?)

Instead of passing five boolean parameters, let’s create a binary truth table that utilizes sequences of 1’s and 0’s which result in multiplication of a prime number to create one unique uint that will store the payment preferences of that specific smart contract.

This would be a sequence of 5 1’s and 0’s, resulting in 2⁵ combinations (32), as written in the table below. The first position on the left represents DAI and whether or not the smart contract will accept it (0 = No, 1 = Yes). The second position represents TrueUSD, and so forth.

The very top column represents the prime number which we will multiply to acquire the final “Payment Preference” number on the right. Whether this number is part of our equation or not is determined by the corresponding 0/1.


DAI — TrueUSD — Tether — Basis — Haven
3–5–7–11–13
0–0–0–0–0 = 2 … no Stablecoin accepted
0–0–0–0–1 = 13 … only Haven accepted
0–0–0–1–0 = 11 … only Basis accepted
0–0–0–1–1 = 143
0–0–1–0–0 = 7 … only Tether accepted
0–0–1–0–1 = 91
0–0–1–1–0 = 77
0–0–1–1–1 = 1,001
0–1–0–0–0 = 5 … only TrueUSD accepted
0–1–0–0–1 = 65
0–1–0–1–0 = 55
0–1–0–1–1 = 715
0–1–1–0–0 = 35
0–1–1–0–1 = 455
0–1–1–1–0 = 385
0–1–1–1–1 = 5,005
1–0–0–0–0 = 3 … only DAI accepted
1–0–0–0–1 = 39
1–0–0–1–0 = 33
1–0–0–1–1 = 429
1–0–1–0–0 = 21
1–0–1–0–1 = 273
1–0–1–1–0 = 231
1–0–1–1–1 = 3,003
1–1–0–0–0 = 15 … only DAI & TrueUSD accepted (my preference!)
1–1–0–0–1 = 195
1–1–0–1–0 = 165
1–1–0–1–1 = 2,145
1–1–1–0–0 = 105
1–1–1–0–1 = 1,365
1–1–1–1–0 = 1,155
1–1–1–1–1 = 15,015 … all stablecoins accepted

The resulting number on the right can be referenced later using modulo calculations within modifiers.

Let’s imagine a unique financial contract that accepts a stablecoin in return for releasing the escrowed funds (Ethereum, ERC20 Tokens, ERC721 Trinkets). These funds are already present within the contract, and we are assuming there is one unique buyer who will receive the funds (public address Buyer).

// Contract is initialized with _paymentMethod = 15, then stored in the Payment_Preference variable. This will enable payments of either DAI or TrueUSD.
constructor (uint _paymentMethod) {
Payment_Preference = _paymentMethod;
}

// A modifier to determine if payment is valid.
modifier isValid(uint _mod) {
require( (Payment_Preference % _mod) == 0);
_;
}

// A set of functions to initiate payment acceptance.
function acceptDAI()     isValid(3) { ... }
function acceptTrueUSD() isValid(5) { ... }
function acceptTether() isValid(7) { ... }
function acceptBasis() isValid(11) { ... }
function acceptHaven() isValid(13) { ... }

The next step would be to modularize payment acceptance functions, condensing all functions of the form accept<Stablecoin>() into a single modular function that accesses a key:value pair structure corresponding to the Stablecoin and prime number used for division.

Does this optimize our gas costs? Maybe — it could potentially increase them.

Can this lead to more modularity in our code? Definitely.

Ultimately, this is a method I’m experimenting with to reduce the number of arguments passed through a constructor function. It could lead to more useful applications as other mechanisms are introduced to a smart contract.


John Quarnstrom
www.inveth.io (Temporarily Offline)