0

I'm developing a smart contract handling document signing. Mainly, there are two things to consider 1) Signers 2) Sign Types.

1) Signers: Mandatory sign the document
2) Types: Types of signs such as eSign, hardware key, etc.

Currently, data is stored in this way:

Agreement
   |
Sign Types
   | 
Signers

Code:

struct Agreement {
    bytes32 id;                                                 
    address owner;                                             
    string fileHash;                                           
    address[] signers;                                         
    uint256 timestamp;                                          
    uint256[] types;
    mapping(uint256 => mapping(address => Stamp)) stamps;    
}

struct Stamp { 
    address signer;
    uint256 blockNumber;
    uint256 timestamp;
}

function createAgreement(
    bytes32 _id, 
    address _owner,  
    string memory _fileHash,
    address[] memory _listOfSigners,
    uint256[] memory _types
)   public returns (bool success) {

    agreements[_id].id = _id;
    agreements[_id].owner = _owner;
    agreements[_id].fileHash = _fileHash;
    agreements[_id].timestamp = block.timestamp;
    agreements[_id].signers = _listOfSigners;
    agreements[_id].types = _types;

    return true;
}

It was working fine till now, but there are changes of optional signers.

Optional signers can optionally sign the document (consider optional signers always in a group of two or more). Meaning that a single person from the group can sign and the document should be considered as a signed.

To avoid an array of arrays, I have used mappings and structs as follows.

struct Agreement {
    bytes32 id;                                                 
    address owner;                                             
    string fileHash;                                           
    address[] signers;                                         
    uint256 timestamp;                                          
    uint256[] types;
    mapping(uint256 => mapping(address => Stamp)) stamps; 
    mapping(uint256 => OptionalSignerGroup[]) optSignerGroup;   
} 

struct OptionalSignerGroup {
     uint256 groupId;
     mapping(uint256 => OptionalSigner[]) signers;
   }

struct OptionalSigner {
    address signer;
}

But, I'm not sure how to add and retrieve data into optional signer groups.

Here is the new flow

          Agreement
             |
           Sign Types
         |         |
Signers(mandatory) Optional Signer groups
                      |                    
                   Signers
  • Too broad. Please post your actual state variables (not just structure declarations), and then explain what you want to add where. Also, the array of arrays of integers [[1,2], [3,4], [5,6]] doesn't fit into any of your types, so please provide a relevant example! – goodvibration Nov 18 '19 at 11:28
  • @goodvibration: updated the question, let me know if anything is still unclear! – Div Nov 18 '19 at 12:05
1

This looks like two or more questions, to me.

A high-level question is the overall data layout. I'm not convinced this layout is ideal. It is usually best to think about readability and simplicity first and optimize later. With that in mind, I would probably tackle it with this: https://github.com/rob-Hitchens/UnorderedKeySet/blob/master/contracts/HitchensUnorderedAddressSet.sol

The idea there is to abstract the arrays and mappings into logical "sets" with useful functions including removal, count and exists. It uses both an array and a mapping which may not be the ideal optimization, but the abstraction can help work out the functionality in the early stage. It will be possible to reduce things to either an array or a mapping once the minimum requirements are clear.

If I understand the original question correctly, it seems to focus on adding an element to an array of a struct that is itself contained in a mapping.

pragma solidity 0.5.11;

contract MappingStructArray {

    struct S1 {
        uint a;
        bool b;
    }

    struct S2 {
        mapping(uint => S1[]) s1Arrays;
    }

    mapping(uint => S2) S2Structs;

    function pushS1(uint key, uint g, uint a, bool b) public {
        S2 storage s2 = S2Structs[key];
        S1 memory s1 = S1({
            a: a,
            b: b
        });
        s2.s1Arrays[g].push(s1);
    }
}

This is an admittedly simplified example for clarity.

Hope it helps.

| improve this answer | |
  • Thanks for the answer! Yes, need to revise the layout. However, I couldn't find the best way to avoid loops. In my current contract, I have to add a loop to add data in to optional signers (Same for retrieve the data) as there are three layers: Agreement -> types -> groups -> signers – Div Nov 19 '19 at 6:19
  • Thanks for accepting my answer. A common approach is to invert control so that iteration is handled by an offchain entity, such as a server. I would probably start with the logical sets and insert/remove members - 1 per transaction. It might be sufficient to use a mapping, such as OpenZeppelin's role-based access control. github.com/OpenZeppelin/openzeppelin-contracts/blob/master/… It's not a perfect match but it could give you ideas. – Rob Hitchens Nov 19 '19 at 7:46
  • Storage layout is usually the most important thing to sort out. It's imperative to arrange things so everything necessary is within reach at all times. – Rob Hitchens Nov 19 '19 at 7:46
  • In case the Libary's purpose isn't clear, it is a candidate replacement for both mappings and dynamic arrays that sort of gives the best of both by using them in concert. – Rob Hitchens Nov 19 '19 at 7:48
  • Thanks for the detailed information! I will give a try. Also will accept this as the answer. – Div Nov 19 '19 at 7:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.