Agreeable Smart Contracts
Tags: ethereum, smartcontact
Published: 2020-08-03 16:16:50

---
title: Agreeable Smart Contracts
published: true
date: 2020-08-02 14:28:03 UTC
tags: Ethereum,smartcontact
canonical_url: https://blog.coincodecap.com/agreeble-smart-contracts?utm_source=rss&utm_medium=rss&utm_campaign=agreeble-smart-contracts
---

Who decides whether to terminate a smart contract? Who decides whether a smart contract should change state?

The answer, of course, is that who decides these things is encoded into the [smart contract](https://blog.coincodecap.com/tag/smart-contact) itself, and it will be different for different contracts.

For example, it may be only the smart contract owner who may terminate the contract. Alternatively, maybe the smart contract gives that ability to the two smart contract users who must agree amongst themselves. Or to 3 of the 4 smart contract users who must agree amongst themselves. There are endless possibilities.

We can make this easier, by providing our smart contracts with _agreement_ objects, that ensure that the smart contract enforces those _mult-signature_ agreements between users before doing whatever has been agreed, changing state or maybe even changing the smart contract itself. The choice is endless. For instance, [here](https://medium.com/coinmonks/coding-upgradeable-smart-contracts-fadf808cc7ee) is an article by this author which describes changing a smart contract following agreement between the smart contract owner and the two users.

A real-world example could be a smart contract that handles _last will and testament_. In this case, there may be various phases of generating the document, perhaps by some of many solicitors, followed by an approval phase comprising some solicitors and some family members, followed by an execution phase comprising, again, some solicitors and the executors. There may be other phases, each of which comprises various numbers of users in different roles.

**Also Read: [Proposing Future Ethereum Access Control](https://blog.coincodecap.com/proposing-future-ethereum-access-control)**

### Example

For this article, we will transition an example smart contract through each of several states, but at each transition only when an agreement between users has been reached.

In this example smart contract, there are 4 users.

The first transition requires the first 2 of the users to agree to something before taking place. 

The next transition requires the last 3 of our 4 smart contract users to agree before the smart contract can transition to the penultimate state.

In the penultimate state, we require any 3 out of the 4 users to agree in order to facilitate the transition to the terminated state. 

Here is a UML State Machine diagram illustrating the states and transitions:

![UML state machine diagram for example smart contract](https://cdn-images-1.medium.com/max/880/1*CKzd3wgC7sEGUkOrVIF_sQ.png)<figcaption>UML state machine diagram for example smart contract</figcaption>

See [this](https://medium.com/coinmonks/state-machines-in-solidity-9e2d8a6d7a11) article by the same author for a discussion of State Machines in Solidity.

The actions of each of the 4 user must be encoded into the smart contract.

```
**User Actions**

user1 transition from Await12 to Await234
        possibly transition from Await3of4 to Terminated

user2 transition from Await12 to Await234
        transition from Await234 to Await3of4
        possibly transition from Await3of4 to Terminated

user3 transition from Await234 to Await3of4
        possibly transition from Await3of4 to Terminated

user4 transition from Await234 to Await3of4
        possibly transition from Await3of4 to Terminated
```

Only 3 of the 4 users are required to agree for the ultimate transition.

### Solution Options

Amongst the many possible solution options, we will consider these options for implementing the agreement objects:

```
1) Smart Contract using Accounts
2) Smart Contract using Roles
3) Using Library For Struct and Accounts
4) Using Library For Struct and Roles
```

Smart Contracts are not ideal for implementing objects in Solidity, because they carry a large overhead — see below.

Using Library For Struct is the main feature provided by Solidity to implement objects.

There is a third option, which is using inheritance as a substitution for composition, but because the example smart contract has multiple agreement objects, that is not applicable here.

[This](https://medium.com/coinmonks/class-features-provided-by-solidity-84ee97840666) article by the the same author discusses class features provided by Solidity in more detail.

The Accounts/Roles options are considered because the implementation could use roles instead of accounts — that decision typically depends on whether your smart contract has more than one user performing the same role. There is plenty of information about role-based access control available. See [this](https://docs.openzeppelin.com/contracts/3.x/api/access#AccessControl) helpful OpenZeppelin contribution for instance.

In practice, we only need to implement 3 of the 4 options and deduce whether it is worth implementing the 4th option. We will not bother with option (2) unless we encounter a compelling reason to implement it.

![Photo by Patrick Hendry on Unsplash](https://blog.coincodecap.com/wp-content/uploads/2020/08/0_UIIMUri1ZOL2iVNp.jpeg)<figcaption>Photo by <a href="https://unsplash.com/@worldsbetweenlines?utm_source=medium&amp;utm_medium=referral" rel="noreferrer noopener" target="_blank">Patrick Hendry</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral" rel="noreferrer noopener" target="_blank">Unsplash</a></figcaption>

### Smart Contracts

Here is the partially completed example smart contract, which fulfils the UML state machine design above, and provides logical class objects for subsequent implementation:

```
**MultipleAgreementContract**

contract MultipleAgreementContract {

    // provide agreement objects
    ... agreement12;
    ... agreement234;
    ... agreement3of4;

    constructor(address[4] memory accounts) public {
        // initialise all agreement objects
    }

_enum State { Await12, Await234, Await3of4, Terminated }  
    State public state = State.Await12;_  

    function **transition12** (address account) public {
_require(state == State.Await12);_
        uint object = 123;
        if (agreement12.agree(object, account) > 0) 
            state = State.Await234;
    }

    function **transition234** (address account) public returns (bool) {
_require(state == State.Await234);_
        uint object = 456;
        if (agreement234.agree(object, account) > 0) 
            state = State.Await3of4;
    }

    function **transition3of4** (address account) public returns (bool) {
_require(state == State.Await3of4);_
        uint object = 789;
        if (agreement3of4.agree(object, account) > 0) 
            state = State.Terminated;
    }
}
```

This smart contract contains the state machine. This is simply implemented in the smart contract using _enum State_, _require(state == X)_ and _state = State.X._ The _state_ is made public in order to assist with testing (shown later).

The smart contract contains the **transition** functions that must be invoked by the users in order to attempt transition to the next state.

The state changing logic of the transition functions will only be executed if the function is called in the correct state. Each transition function must be called by each of the users who are responsible for the transition, but in any order. For instance, in order to transition from _State.Await12_ to _State.Await234,_ both user 1 and user 2 must invoke the transition function **_transition12_**. Following the second such invocation, the machine state will be changed.

The different solution options are discussed further in the following sections.

#### 1) Smart Contract using Accounts

Here is the MultipleAgreementContract constructor for this solution option:

```
    **MultipleAgreementContract for Smart Contract using Accounts**

import "Agreement2.sol";
import "Agreement3.sol";
import "Agreement3of4.sol";

contract MultipleAgreementContract {

    Agreement2 agreement12;
    Agreement3 agreement234;
    Agreement3of4 agreement3of4;

    constructor( address[4] memory accounts ) public {
        agreement12 = new Agreement2([accounts[0], accounts[1]]);
        agreement234 = new Agreement3(
                           [accounts[1], accounts[2], accounts[3]]);
        agreement3of4 = new Agreement3of4(accounts);
    }
```

Here, each of the agreement objects has its own smart contract and is instantiated with the user accounts that are required to achieve agreement.

The implementation contracts are along these lines:

```
                             **Agreement2**

// agreement between 2 accounts

import "AgreementBase.sol";

contract Agreement2 is AgreementBase {
    constructor( address[2] memory \_accounts ) public {
        accounts.push(\_accounts[0]);
        accounts.push(\_accounts[1]);
        sigs = 2;
    }
}
```

The implementations of Agreement3 and Agreement3of4 are similar. The AgreementBase contract that these implementation contracts require is:

```
                            **AgreementBase**

// agreement between configured accounts

import "DiBits.sol";

contract AgreementBase {

    address[] accounts; // set accounts in derived class
    uint sigs; // // set minimum signatures in derived class

    uint object;
    uint bitMasks;

    function agree(uint \_object, address account) public returns
    (int) {

        // returns <0:new object, 0:no change, 1:agreed object

        uint bitMask;
        for (uint i = 0; i < accounts.length; i++) {
            if (account == accounts[i]) {
                bitMask = 1 << i;
                break;
            }
        }
        require(bitMask != 0, "Unknown sender");

        if (object != \_object) {
            object = \_object;
            bitMasks = bitMask;
            return -1;
        }

        bitMasks |= bitMask;

        if (DiBits.count(bitMasks) >= sigs) {
            return 1;
        }
    }
}
```

The _accounts_ array and _sigs_ value must be configured in derived contracts.

The _sigs_ variable is used for the Agreement3of4 class, and generally for mutli-sig agreements requiring agreement amongst some of the users. This feature of the agreement objects enables the smart contract to determine when agreement has been reached. That could be 1 of 2, 1 of 5, or 3 of 4 as in this case.

Events may be usefully emitted when a new object is detected, and when agreement is reached. This is not shown for clarity.

The library function DiBits.count(uint) simply counts the number of set bits.

#### 3) Using Library For Struct and Accounts

Here is the MultipleAgreementContract constructor for this solution option:

```
**MultipleAgreementContract for Using Library For Struct and Accounts**

import "Agreement.sol";

contract MultipleAgreementContract {
    using AgreementFuns for Agreement;

    Agreement agreement12;
    Agreement agreement234;
    Agreement agreement3of4;

    constructor(address[4] memory accounts) public {
        agreement12.initialise([accounts[0], accounts[1]]);
        agreement234.initialise(
                      [accounts[1], accounts[2], accounts[3]]);
        agreement3of4.initialise(accounts);
        agreement3of4.sigs = 3;
    }
```

The line _using AgreementFuns for Agreement_, causes the compiler to use the functions in the AgreementFuns library for Agreement objects. See the Solidity documentation [here](https://solidity.readthedocs.io/en/latest/contracts.html#using-for) for more information.

The constructor initialises the 3 agreement objects with the user accounts required to fulfil the agreements. The last line resets the minimum number of accounts that must agree to 3 (otherwise it would have been all 4 accounts).

Here is the agreement inclusion file:

```
**Agreement using address accounts**

// agreement between given accounts

import "DiBits.sol";

struct Agreement {
    address[] accounts; // user accounts are recorded here
    uint sigs; // the min number of accounts required to agree

    uint object; // the object to be agreed e.g. a contract address
    uint bitMasks; // the set of agreed accounts
}

// -----------

library AgreementFuns {

    function initialise( **Agreement storage self** , address[2] memory 
    accounts) public {
**self**.accounts.push(accounts[0]);
**self.** accounts.push(accounts[1]);
**self**.sigs = 2;
    }

    // also initialisers for address[3 and 4] memory accounts

    function agree( **Agreement storage self** , uint \_object, address 
    sender) public returns (int) {

        // returns <0:new object, 0:no change, 1:agreed object

        uint bitMask;
        uint length = **self**.accounts.length;
        for (uint i = 0; i < length; i++) {
            if (sender == **self**.accounts[i]) {
                bitMask = 1 << i;
                break;
            }
        }
        require(bitMask != 0, "Unknown sender");

        if ( **self**.object != \_object) {
**self**.object = \_object;
**self**.bitMasks = bitMask;
            return -1;
        }

**self**.bitMasks |= bitMask;

        if (DiBits.count( **self**.bitMasks) >= **self**.sigs) {
            return 1;
        }
    }
}
```

This is substantially the same as presented above. The differences are in **bold**.

As stated above, events may be usefully emitted when a new object is detected, and when agreement is reached. This is not shown for clarity.

When using smart contracts, we can derive new contracts from base contracts at no cost to the code.

However, when using libraries, we are a little more restricted because libraries cannot have base classes or be base classes. So all possible initialisers have to be added to the library to retain data encapsulation. For this reason, it may be preferable to use a dynamic array in the initialiser, and avoid multiple initialisers. That effectively moves the configuration of the Agreement accounts data to the calling routine. Hence, we haven’t bothered with that here.

#### 4) Using Library For Struct and Roles

Here is the MultipleAgreementContract constructor for this solution option:

```
**MultipleAgreementContract for Using Library For Struct and Roles**

import "Agreement.sol";

contract MultipleAgreementContract {
    using AgreementFuns for Agreement;

    Agreement agreement12;
    Agreement agreement234;
    Agreement agreement3of4;

    mapping(address => uint8) **roles** ;

    constructor(address[4] memory accounts) public {
        roles[accounts[0]] = 1;
        roles[accounts[1]] = 2;
        roles[accounts[2]] = 3;
        roles[accounts[3]] = 4;

        agreement12.initialise([1, 2]);
        agreement234.initialise([2, 3, 4]);
        agreement3of4.initialise([1, 2, 3, 4]);
        agreement3of4.sigs = 3; // multi-sig 3 of 4
    }
```

This is substantially the same as the previous solution option, except for the addition of a **mapping** from the account addresses to the **roles** identifiers, and initialisation of same in the constructor — we have just used the Role numbers 1 to 4. Lastly, we pass the role numbers, not the account addresses, through to the agreement objects.

This facility means that, if needed, we can easily add user accounts which map to the current roles without changing anything else in the smart contract, for instance:

```
              **Example Additional User for existing Role**

    constructor(address[5] memory accounts) public {
        roles[accounts[0]] = 1;
        roles[accounts[1]] = 2;
        roles[accounts[2]] = 3;
        roles[accounts[3]] = 4;

        **roles[accounts[4]] = 2; // additional account for role 2**

        agreement12.initialise([1, 2]);
        agreement234.initialise([2, 3, 4]);
        agreement3of4.initialise([1, 2, 3, 4]);
        agreement3of4.sigs = 3; // multi-sig 3 of 4
    }
```

The agreement inclusion file is substantially the same as the previous example. We simply replace all _account addresses_ with _role uints:_

```
                   **4) Agreement using uint roles**

// agreement between roles

struct Agreement {
    uint[] roles; // roles are recorded here

    // as before
}
```

That also means that there is an opportunity here to use a variable which requires less than 32bytes to record a role. As well as using an array of uints to contain the roles, we will try using a bytes32 variable, with 1 byte allocated to each role:

```
                  **4a) Agreement using bytes32 roles**

struct Agreement {
    uint len; // number of roles
    bytes32 roles; // roles are recorded here
    uint sigs; // the min number of roles required to agree

    uint bitMasks;
    uint object;
}
```

Obviously, the initialisation and agreement functions will need a little fettling.

Lastly, we would like to take the opportunity to optimise the gas consumption by minimising the use of Ethereum’s expensive storage slots:

```
           **4b) Agreement optimising use of storage space**

struct Agreement {
    uint160 object; // large enough to hold address
    uint64 roles; // up to 16 roles of 16 values each
    uint8 len; // number of roles
    uint8 sigs; // // the min number of roles required to agree
    uint8 bitMasks; // set of agreements reached so far
}
```

This whole struct fits within one 256bit storage word. Despite the additional machinations required to read and write the data, the gas consumption for storage access will be vastly reduced.

### Testing

![Photo by Science in HD on Unsplash](https://cdn-images-1.medium.com/max/880/0*7THiUlMe03dhNLt6)<figcaption>Photo by <a href="https://unsplash.com/@scienceinhd?utm_source=medium&amp;utm_medium=referral" rel="noreferrer noopener" target="_blank">Science in HD</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noreferrer noopener" target="_blank">Unsplash</a></figcaption>

The fundamental approach to testing used here is to step the smart contract under test through the machine states originally specified (sunny day testing). Other tests are required to ensure that the smart contract is working properly. The test smart contract is:

```
                     **TestMultipleAgreementContract**

contract TestMultipleAgreementContract {
    address constant account1 = 0x...1111111111;
    address constant account2 = 0x...2222222222;
    address constant account3 = 0x...3333333333;
    address constant account4 = 0x...4444444444;

    MultipleAgreementContract mac;

    constructor() public {
        mac = new MultipleAgreementContract(
                      [account1, account2, account3, account4]);
    }

    function test12() public {
        require(mac.state() == ...Await12);
        mac.transit12(account1);
        require(mac.state() == ...Await12);
        mac.transit12(account2);
        require(mac.state() == ...Await234); 
    }

    function test234() public {
        require(mac.state() == ...Await234);
        mac.transit234(account2);
        require(mac.state() == ...Await234);
        mac.transit234(account3);
        require(mac.state() == ...Await234);
        mac.transit234(account4);
        require(mac.state() == ...Await3of4);
    }

    function test3of4() public {
        require(mac.state() == ...Await3of4);
        mac.transit3of4(account1);
        require(mac.state() == ...Await3of4);
        mac.transit3of4(account2);
        require(mac.state() == ...Await3of4);
        mac.transit3of4(account2);
        require(mac.state() == ...Await3of4);
        mac.transit3of4(account4);
        require(mac.state() == ...Terminated);
    }
}
```

The test contract is the same, irrespective of the smart contract being tested.

#### Gas Consumption

Having ascertained that all the solution contracts pass these simple tests, we can measure the gas consumed while doing so.

The gas consumption measuring smart contract to do that is along these lines:

```
                 **GasTestMultipleAgreementContract**

import "DcGas.sol";

contract GasTestMultipleAgreementContract is DcGas {
    TestMultipleAgreementContract testMac;

    function testConstruct() internal {
        testMac = new TestMultipleAgreementContract();
    }
    function gtestConstruct() public {
        gasCostFun("testConstruct", testConstruct);
    }

    function testTransit12() internal {
        testMac.test12();
    }
    function gtestTransit12() public {
        gasCostFun("testTransit12", testTransit12);
    }

    function testTransit234() internal {
        testMultipleAgreementContract.test234();
    }
    function gtestTransit234() public {
        gasCostFun("testTransit234", testTransit234);
    }

    function testTransit3of4() internal {
        testMultipleAgreementContract.test3of4();
    }
    function gtestTransit3of4() public {
        gasCostFun("testTransit3of4", testTransit3of4);
    }
}
```

We used a method of measuring gas consumption we have used many times before. See [this](/coinmonks/gas-cost-of-solidity-library-functions-dbe0cedd4678) article by the same author for the tehnique.

### Results

The gas consumed during execution of the tests was collated and entered into spreadsheets. Here is the total gas consumption for the solution options (1), (3) and (4), and the additional solutions introduced above, (4a) and (4b).

![Gas consumption of executing the test smart contracts](https://cdn-images-1.medium.com/max/880/1*FG2EAU_luwmvrdny1wLsVA.png)<figcaption>Gas consumption of executing the test smart contracts</figcaption>

This chart clearly shows the high gas cost of using smart contracts to represent objects in the first column 1) Contract/Accounts.

Gas consumption is lower when employing _using library for struct_, as shown in 3) Library / Accounts.

Changing from using accounts with our libraries to using roles does consume more gas, as indicated by 4) Library / Roles.

However, we can mitigate that by employing smaller variables to hold our role number as shown in 4a) Library / Roles / bytes32.

Lastly, it can clearly be seen that optimising the Agreement struct to use bit fields in order to reduce the usage of expensive storage has a marked affect, as indicated by 4b) Library / Roles / Optimisation.

Assuming that the smart contract is long lived, and could transit from the ultimate state back to the initial state many times, the gas consumed during smart contract construction may be less relevant. Here is a graph of gas consumption ignore the construction costs:

![Gas consumption of transitioning the test smart contracts](https://cdn-images-1.medium.com/max/880/1*rUFUzJaJWqmNu18Oog9OHQ.png)<figcaption>Gas consumption of transitioning the test smart contracts</figcaption>

This shows a lot more clearly that the optimised data structure for roles consumes vastly less gas than the other options.

### Conclusions

Use Agreement objects whenever you need several users to agree on the same object before changing smart contract state.

Unless you have a good reason, avoid the use of _contracts_ to represent objects and employ _using library for struct_ instead.

If it suits the requirements of your smart contract, use agreement _roles_ in preference to agreement _accounts_.

As well as additional flexibility in terms of enabling multiple users to occupy the same role, there is an opportunity to optimise the agreement role data and vastly reduce use of Ethereum’s expensive storage slots.

### Bio

Jules Goddard is Co-founder of Datona Labs, who provide smart contracts to protect your digital information from abuse.

[website](https://io.datonalabs.org) — [twitter](https://twitter.com/JulianGoddard6) — [discord](https://discord.gg/sSnvK5C) — [GitHub](https://github.com/Datona-Labs)

The post [Agreeable Smart Contracts](https://blog.coincodecap.com/agreeble-smart-contracts) appeared first on [CoinCodeCap Blog](https://blog.coincodecap.com).