2
votes

We have a problem with contract redeploying. Each time when some logic is changed during new contract version deployment we are loosing all contract related data (which are stored in arrays, mappings). Then we need to execute data load procedures in order to restore environment to desired state which is time consuming action. I tried to split contract to tow ones (AbcDataContract, AbcActionsContract) but faced with problem of accessing to the mappings : Error: Indexed expression has to be a type, mapping or array (is function (bytes32) view external returns (uint256)) Initial contract :

contract AbcContract {

EntityA[] public entities;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;

/* Events */
event Event1(uint id);
event Event2(uint id);

/* Structures */
struct EntityA {
    string field1;
    string field2;
    bool field3;
    uint field4;
    Status field5;
}

enum Status {PROPOSED, VOTED, CONFIRMED}

function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)

}

Refactored contracts :

contract AbcDataContract {

EntityA[] public items;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;

/* Events */
event Event1(uint id);
event Event2(uint id);

/* Structures */
struct EntityA {
    string field1;
    string field2;
    bool field3;
    uint field4;
    Status proposalStatus;
}

enum Status {PROPOSED, VOTED, CONFIRMED}

}

contract AbcActionsContract {

AbcDataContract abcDataContract;

/* constructor */
function AbcActionsContract(address _AbcDataContract) {
    abcDataContract = AbcDataContract(_AbcDataContract);
}

/* accessing to the mapping like abcDataContract.mapping1[someId] will raise Solidity compile error  */
function function1(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping2[someId] will raise Solidity compile error  */
function function2(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping3[someId] will raise Solidity compile error  */
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)

}

We would like to implement approach like we have in DB development when logic changes in stored procedures/views/other not data objects usually does not affect data itself. What is the best design solution for this problem ?

2

2 Answers

2
votes

The first part of your question is fairly easy. To access a public mapping in another contract, simply use ():

abcDataContract.mapping1(someId)

Of course, you can also provide your own access methods to AbcDataContract instead of using the public mapping as well. If you go down this path, I'd recommend going through an interface to access your data contract

As for the design part of your question, it looks like you're on the right track. Separating your data store into its own contract has huge benefits. Not only is it much easier to deploy since you don't have to worry about migrating your data, but it's also much cheaper to deploy the new contract.

That being said, there's a couple things I want to point out with the refactored version you posted.

  1. It's hard to tell what you're planning on doing with Struct1. There's no reference to it in your pseudocode. You can't return structs from functions in Solidity unless they are internal calls (or you explicitly decompose the struct).
  2. Similarly, you can't return strings between contracts. If you plan on using Struct1.field1/2 in AbcActionsContract, you'll need to convert them to bytes32.
  3. You'll probably want to move your event definitions into your business logic contract.

Separating your data store from your business logic is a key component to upgrading contracts. Using interfaces and libraries help with this. There are several blog posts out there addressing this issue. I personally recommend starting with this one along with a follow up here.

1
votes

Here is approximate design of contracts which should resolve issue with losing data due to deployment some changes in contract's business logic :

contract DomainObjectDataContract {

struct DomainObject {
    string field1;
    string field2;
    bool field3;
    uint field4;
    Status field5;
}

enum Status {PROPOSED, VOTED, CONFIRMED}

//primitives

//getters/setters for primitives

/arrays
DomainObject[] public entities;

//getters(element by id)/setters(via push function)/counting functions

mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;

//getters(element by id/ids)/setters(depends from the mapping structure)/counting functions

}

contract DomainObjectActionsContract {

DomainObjectDataContract domainObjectDataContract;

/*constructor*/
function DomainObjectActionsContract(address _DomainObjectDataContract) {
    domainObjectDataContract = DomainObjectDataContract(_DomainObjectDataContract);
}

/* functions which contain business logic and access/change data via domainObjectDataContract.* Redeploying of this contract will not affect data*/
function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)

}

One of the pending design issues is application pagination capabilities. Let's suppose we have following structure :

    struct EntityA {
    string lessThen32ByteString1;
    string moreThen32ByteString1;
    string lessThen32ByteString2;
    string moreThen32ByteString3;
    bool flag;
    uint var1;
    uint var2;
    uint var3;
    uint var4;
    ProposalStatus proposalStatus;
}
    // 100K entities 
EntityA[] public items;

And we need to return subset of data based on offset and limit to our UI per one contract's function invocation. Due to different limits/errors of Solidity our current functions (with helper functions for string to byte32 conversion, splitting string to a few byte32 parts and so on) looks like this :

    function getChunkOfPart1EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString1Arr, bytes32[100] moreThen32ByteString1PrefixArr, bytes32[100] moreThen32ByteString1SuffixArr) {
}

function getChunkOfPart2EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString2Arr, bytes32[100] moreThen32ByteString2PrefixArr, bytes32[100] moreThen32ByteString2SuffixArr) {
}

function getChunkOfPart3EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bool[100] flagArr, uint[100] var1Arr, uint[100] var2Arr, uint[100] var3Arr, uint[100] var4Arr, ProposalStatus[100] proposalStatusArr,) {
}

Definitely they looks like awful from design perspective but we still have no better solution for pagination. I am not even saying that there is no any query language support, even basic filtering by some field require manual implementation.