1

In order to build a somewhat complex Dapp, I need to manage "Contributions". To do this, I roughly see two patterns:

  • A Factory-pattern, in which each Contribution is it's own Contract/Instance.
  • A Mapping(?)-pattern, in which each Contribution is an entry in a mapping of a main contract.

The Factory pattern is explained in basics at Deploy contract from contract in Solidity. Roughly, it would work as follows:

  1. A factory contract has a create() method, which creates a new contract on the blockchain.
  2. Clients should now interact with this contract directly.
  3. Clients can now place an offer() on this contract.
  4. A client for the owner, can now claim() the offer, getting all the funds and closing the contract.

The mapping-pattern, for the lack of a better name, is a single contract that manages the state of all the Contributions in an stored mapping. Like this:

  1. A create adds a new contribution struct to a mapping and stores this.
  2. Clients can now place an offer() on this contract, which results in some variables in the internal mapping changing.
  3. A client for the owner, can now claim() the offer, getting all the funds associtated in the mapping and removing the Contribution from the mapping.

Below is some pseudo-ish code to explain how both patterns might look with Solidity. Note that they are not exactly similar in features (e.g. the mapping allows only one contribution per address) but those are mostly details that would make the examples even more convoluted.

The Factory pattern:

/**
 * +@Contribution+: A contract that holds a Contribution
 **/
contract Contribution {
  string data;
  bool forSale;
  address owner;

  function Contribution(string _data, address _contributor) {
    data = _data;
    owner = _contributor;
  }

  modifier onlyOwner {
    require(msg.sender == owner);
    _;
  }

  function offer() public payable {
    require(msg.value > 0);
    require(forSale == true);
    forSale = false;
  }

  function deny() public onlyOwner {
    require(forSale == true);
    forSale = true;
  }

  function claim() public onlyOwner {
    require(address(this).balance > 0);
    selfdestruct(owner);
  }
}

/**
 * +@ContributionsFactory+: Creates a Contribution and keeps this in a list of
 * all contributions
 **/
contract ContributionFactory {
  address[] contributions;

  function contribute(string _data) public returns(address) {
    address contribution = new Contribution(_data, msg.sender);
    contributions.push(contribution);
    return contribution;
  }
}

And the mapping pattern:

/**
 * +@Contributions+: A contract that manages all contributions
 **/
contract Contributions {
  struct Contribution {
    string data;
    bool forSale;
    uint256 offer;
  }

  mapping (address => Contribution) contributions;

  function contribute(string _data) public returns(address) {
    contributions[msg.sender] = Contribution(_data, false, 0);
  }

  function offer(address _contributor) payable {
    require(msg.value > 0);

    Contribution contribution = contributions[_contributor];
    require(contribution.forSale == true);

    contribution.forSale = false;
    contribution.offer = msg.value;

    contributions[_contributor] = contribution;
  }

  function deny(address _contributor) public {
    Contribution contribution = contributions[_contributor];
    require(contribution.forSale == true);
    contribution.forSale = true;
    contributions[_contributor] = contribution;
  }

  function claim(address _contributor) public {
    require(msg.sender == _contributor);
    require(address(this).balance > 0);

    msg.sender.transfer(contributions[_contributor].offer);
    delete(contributions[_contributor]);
  }
}

I can see both patterns having some pro's and cons, but I'd like to know:

  • What is the common way to deal with such set-ups in contracts/Dapps?
  • Are there any studies, or write-ups that explain such patterns for Ethereum/solidity?
  • Did I miss something obvious that makes choosing one over the other easier?

Some pro's and con's that I can imagine:

  • creating contracts is relatively expensive, compared to adding an entry to mappings. Is this always true?
  • the mapping-contract quickly becomes complex because many preconditions must use data in the mapping.
  • the factory-contract needs either a callback from the created-contract (missing in the examples) in order to remove entries when a contract is fullfilled. OR
  • the factory must act as proxy through wich such calls go; instead of direct interaction with the created contract, clients interact with the factory, who then delegates such calls to the contract itself; in order to keep the mapping correct.

Your Answer

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

Browse other questions tagged or ask your own question.