Building a Project with SE-2 | Crowd Fund | Part Eight | The Refactor

WebSculpt
4 min readFeb 16, 2024

--

Image from Shubham Dhage on Unsplash

Up until now, all of this Smart Contract’s code has been in one file, but now this file has grown quite large. We are going to clean it up a bit. Here are some links to the project files that are helpful to have open while going through this blog:

Let’s talk about how to refactor a Smart Contract

There is a discussion over which is better… Inheritance or Composition? Let’s take a look at the two.
This is an example of Composition:

contract A  {

function doSomething(uint _sentToMeTwo) public pure returns(uint){
return(_sentToMeTwo);
}

}

contract B {

A a;

constructor() {
a = new A();
}

function doSomething(uint _sentToMe) public view returns(uint) {
return a.doSomething(_sentToMe);
}

}

And, here is an example of Inheritance:

contract A  {

function doSomething() public view returns(uint){
return(...);
}

}

contract B is A {

}

Inheritance is more of an “Is-a” relationship: If B is A, then A is basically rolled up into B.
B can still call doSomething()

An example of this that you are probably already used to is OpenZeppelin’s Ownable.sol.
When contract YourContract is Ownable you can call onlyOwner.

What is the actual difference?

When you inherit from it, you become it (in a way) … Just look at the code: YourContract is SomeOtherContract
If you look at the example of Composition — you will see more of a “has-a” relationship:
return a.doSomething();

Composition is giving us an “instance variable” (of the other object) — allowing us to implement a “has-a” relationship.

These are Object-Oriented Programming concepts, but what we are doing is more like Contract-Oriented Programming. Composition is harder to visualize when you are going from contract-to-contract (especially considering security-concerns like reentrancy and replay attacks). That’s not the only reason I chose to go with Inheritance for this particular refactor. It simply makes sense because of how interrelated all of the logic in this Smart Contract is.
As for now, the main reason to refactor is because this is a blog, and what we’ve worked on so far is in-the-way — I want to clean up our work area a bit before we move along.

More links on the subject:

Changes before refactoring

I have some plans for what to do with this project next, and there are aspects to how the V3 CrowdFund.sol works that will be a bit in-the-way later on.

Open-ended Fund Runs

Fund Runs will now be open-ended; therefore, no ‘deadline’ that runs out, no ‘target amount’ to reach, and there is no longer a need for a Fund Run to have a ‘status’. We will also be deleting many events, modifiers, and functions.
This cleans up the code a bit before the actual refactor — where the code will be divided into smaller segments/files. Some files will inherit from others in an “Inheritance Tree”.

The Inheritance Tree

  • CrowdFundLibrary.sol (structs)
  • ProfitTaker.sol (contractOwnerWithdraw, getNetWithdrawAmt)
  • MultisigManager.sol (createMultisigProposal, supportMultisigProposal, mappings, etc)
  • CrowdFundManager.sol (donations, payables, createFundRun)
  • CrowdFund.sol ( … working/devving here.)

This compartmentalizes our code a bit. Now we will end up with a solidity file that looks like this!

Your dApp will still work because all of the code (that already works) is simply placed in separate solidity files (that inherit from other files) such that CrowdFund.sol still has access to all of the same functions as before.

Your contracts

contract CrowdFund is FundRunManager

contract FundRunManager is MultisigManager

contract MultisigManager is ProfitTaker

contract ProfitTaker is Ownable, ReentrancyGuard

When Refactoring

Just consider what the Top-Level is that you will need a variable/value/function in. We have an example in our code. The logic that manages Fund Runs ( FundRunManager.sol ) needs to use the mappings: fundRunValues and fundRunOwners; however, these are also vital to the logic that manages Proposals ( MultisigManager.sol ).

If I put these mappings in MultisigManager, then I will also have access to them in FundRunManager.

BUT, if you try to do this with Structs, you are going to have a rough time. That’s why we have CrowdFundLibrary.

Libraries

Libraries can be utilized to create reusable code. Libraries do not have any storage (they are not for changing the state of the contract), but they CAN be used for basic operations based on in-/out-puts. They can also do something very handy…
Libraries can implement some data types.

If you want to pass-around Types like Structs, then you will need to do so in a Library. We want to do … just that.

library CrowdFundLibrary {
struct MultiSigRequest {
uint256 amount;
address to;
address proposedBy;
string reason;
}

struct FundRunValues {
uint256 amountCollected;
uint256 amountWithdrawn;
}

struct DonorsLog {
address donor;
mapping(uint256 => uint256) donorMoneyLog; //mapping(fundRunId => donationAmount)
}
}

ProfitTaker.sol imports the new Library:
import “./CrowdFundLibrary.sol”;

And then … we can still use our Structs like this:

CrowdFundLibrary.FundRunValues storage fundRunVals = fundRunValues[numberOfFundRuns];

Ready to learn more? Now that we have over 700 lines of Solidity refactored, we’ll have a tidy workspace to write more code in. What’s next? Crowd Fund is going the Social Media route!

--

--

WebSculpt

Blockchain Development, coding on Ethereum. Condensed notes for learning to code in Solidity faster.