New startup has created its own Ethereum-compatible blockchain to help connect financial institutions, and the team wants to build smart contracts to automate some company finances to make everyone's lives easier, increase transparency, and to make accounting and auditing practically automatic!
Build Smart contracts ProfitSplitter
using Solidity to perfrom several things:
-
Pay Associate-level employees quickly and easily.
-
Distribute profits to different tiers of employees.
-
Distribute company shares for employees in a "deferred equity incentive plan" automatically.
-
AssociateProfitSplitter.sol
-- Level 1 starter code. -
TieredProfitSplitter.sol
-- Level 2 starter code. -
DeferredEquityPlan.sol
-- Level 3 starter code.
3 different smart contracts distributing to be created:
-
Level One is an
AssociateProfitSplitter
contract. This will accept Ether into the contract and divide the Ether evenly among the associate level employees. This will allow the Human Resources department to pay employees quickly and efficiently. -
Level Two is a
TieredProfitSplitter
that will distribute different percentages of incoming Ether to employees at different tiers/levels. For example, the CEO gets paid 60%, CTO 25%, and Bob gets 15%. -
Level Three is a
DeferredEquityPlan
that models traditional company stock plans. This contract will automatically manage 1000 shares with an annual distribution of 250 over 4 years for a single employee.
Starting project
Navigate to the Remix IDE and create a new contract called using the starter code for respected level as above.
While developing and testing your contract, use the Ganache development chain, and point MetaMask to localhost:8545
, or replace the port with the one set in the workspace.
As Solidity is Object Oriented Programming Language, all contracts must be compiled and deployed first.
Note: Make sure to use the correct compiling version in Remix (^0.5.0 or above) and always use prefunded address from Metamask. Contracts deployment has to be done with 0 wei and Injected Web3 which will connect to 'Metamask' address.
In the Deploy
tab in Remix, deploy the contracts to your local Ganache chain by connecting to Injected Web3
and ensuring Metamask is pointed to localhost:8545
Fill in the contructor parameters with designated employee
addresses
Test the deposit
function by sending various values. Check the employee
balances as and when various amounts of Ether are sent to the contract and ensure the logic is executing properly.
Once testing is done with the contracts, point MetaMask to the Kovan or Ropsten network. Ensure to have test Ether on this network! Kovan_Ether
After switching MetaMask to Kovan, deploy the contracts as before and copy/keep a note of their deployed addresses. The transactions will also be in the MetaMask history, and on the blockchain permanently to explore later Etherscan
Level One: The `AssociateProfitSplitter` Contract
At the top of the contract, define the following public
variables:
-
employee_one
-- Theaddress
of the first employee. Make sure to set this topayable
. -
employee_two
-- Anotheraddress payable
that represents the second employee. -
employee_three
-- The thirdaddress payable
that represents the third employee.
Create a constructor function that accepts:
-
address payable _one
-
address payable _two
-
address payable _three
Within the constructor, set the employee addresses to equal the parameter values. This will allow you to avoid hardcoding the employee addresses.
Next, create the following functions:
-
balance
-- This function should be set topublic view returns(uint)
, and must return the contract's current balance. Since always Ether are to be sent to the beneficiaries, this function should always return0
. If it does not, thedeposit
function is not handling the remainders properly and should be fixed. This will serve as a test function of sorts. -
deposit
-- This function should set topublic payable
check, ensuring that only the owner can call the function.-
In this function, perform the following steps:
-
Set a
uint amount
to equalmsg.value / 3;
in order to calculate the split value of the Ether. -
Transfer the
amount
toemployee_one
. -
Repeat the steps for
employee_two
andemployee_three
. -
Since
uint
only contains positive whole numbers, and Solidity does not fully support float/decimals, deal with a potential remainder at the end of this function sinceamount
will discard the remainder during division. -
There maybe either
1
or2
wei leftover, so transfer themsg.value - amount * 3
back tomsg.sender
. This will re-multiply theamount
by 3, then subtract it from themsg.value
to account for any leftover wei, and send it back to Human Resources.
-
-
-
Create a fallback function using
function() external payable
, and call thedeposit
function from within it. This will ensure that the logic indeposit
executes if Ether is sent directly to the contract. This is important to prevent Ether from being locked in the contract since we don't have awithdraw
function in this use-case.
AssociateProfitSplitter
- Contract Deployment & Confirmation
Deployment | Balances |
---|---|
![]() |
![]() |
Level Two: The `TieredProfitSplitter` Contract
In this contract, rather than splitting the profits between Associate-level employees, calculate rudimentary percentages for different tiers of employees (CEO, CTO, and Bob).
Using the starter code, within the deposit
function, perform the following:
-
Calculate the number of points/units by dividing
msg.value
by100
.- This will allow us to multiply the points with a number representing a percentage. For example,
points * 60
will output a number that is ~60% of themsg.value
.
- This will allow us to multiply the points with a number representing a percentage. For example,
-
The
uint amount
variable will be used to store the amount to send each employee temporarily. For each employee, set theamount
to equal the number ofpoints
multiplied by the percentage (say, 60 for 60%). -
After calculating the
amount
for the first employee, add theamount
to thetotal
to keep a running total of how much of themsg.value
we are distributing so far. -
Then, transfer the
amount
toemployee_one
. Repeat the steps for each employee, setting theamount
to equal thepoints
multiplied by their given percentage. -
For example, each transfer should look something like the following for each employee, until after transferring to the third employee:
-
Step 1:
amount = points * 60;
-
For
employee_one
, distributepoints * 60
. -
For
employee_two
, distributepoints * 25
. -
For
employee_three
, distributepoints * 15
.
-
-
Step 2:
total += amount;
-
Step 3:
employee_one.transfer(amount);
-
-
Send the remainder to the employee with the highest percentage by subtracting
total
frommsg.value
, and sending that to an employee. -
Deploy and test the contract functionality by depositing various Ether values (greater than 100 wei).
-
The provided
balance
function can be used as a test to see if the logic you have in thedeposit
function is valid. Since all of the Ether should be transferred to employees, this function should always return0
, since the contract should never store Ether itself. -
Note: The 100 wei threshold is due to the way we calculate the points. If we send less than 100 wei, for example, 80 wei,
points
would equal0
because80 / 100
equals0
because the remainder is discarded. We will learn more advanced arbitrary precision division later in the course. In this case, we can disregard the threshold as 100 wei is a significantly smaller value than the Ether or Gwei units that are far more commonly used in the real world (most people aren't sending less than a penny's worth of Ether).
-
Deployment | Balances |
---|---|
![]() |
![]() |
Level Three: The `DeferredEquityPlan` Contract
In this contract, manage an employee's "deferred equity incentive plan" in which 1000 shares will be distributed over 4 years to the employee. No need to work with Ether in this contract, but there will storing and setting amounts that represent the number of distributed shares the employee owns and enforcing the vetting periods automatically.
-
A two-minute primer on deferred equity incentive plans: In this set-up, employees receive shares for joining and staying with the firm. They may receive, for example, an award of 1,000 shares when joining, but with a 4 year vesting period for these shares. This means that these shares would stay with the company, with only 250 shares (1,000/4) actually distributed to and owned by the employee each year. If the employee leaves within the first 4 years, he or she would forfeit ownership of any remaining (“unvested”) shares.
-
If, for example, the employee only sticks around for the first two years before moving on, the employee’s account will end up with 500 shares (250 shares * 2 years), with the remaining 500 shares staying with the company. In this above example, only half of the shares (and any distributions of company profit associated with them) actually “vested”, or became fully owned by the employee. The remaining half, which were still “deferred” or “unvested”, ended up fully owned by the company since the employee left midway through the incentive/vesting period.
-
Specific vesting periods, the dollar/crypto value of shares awarded, and the percentage equity stake (the percentage ownership of the company) all tend to vary according to the company, the specialized skills, or seniority of the employee, and the negotiating positions of the employee/company. If you receive an offer from a company offering equity (which is great!), just make sure you can clarify the current dollar value of those shares being offered (based on, perhaps, valuation implied by the most recent outside funding round). In other words, don’t be content with just receiving “X” number of shares without having a credible sense of what amount of dollars that “X” number represents. Be sure to understand your vesting schedule as well, particularly if you think you may not stick around for an extended period of time.
-
Using the starter code, perform the following:
-
Human Resources will be set in the constructor as the
msg.sender
, since HR will be deploying the contract. -
Below the
employee
initialization variables at the top (afterbool active = true;
), set the total shares and annual distribution:-
Create a
uint
calledtotal_shares
and set this to1000
. -
Create another
uint
calledannual_distribution
and set this to250
. This equates to a 4 year vesting period for thetotal_shares
, as250
will be distributed per year. Since it is expensive to calculate this in Solidity, we can simply set these values manually. You can tweak them as you see fit, as long as you can dividetotal_shares
byannual_distribution
evenly.
-
-
The
uint start_time = now;
line permanently stores the contract's start date. We'll use this to calculate the vested shares later. Below this variable, set theunlock_time
to equalnow
plus365 days
. We will increment each distribution period. -
The
uint public distributed_shares
will track how many vested shares the employee has claimed and was distributed. By default, this is0
. -
In the
distribute
function:-
Add the following
require
statements:-
Require that
unlock_time
is less than or equal tonow
. -
Require that
distributed_shares
is less than thetotal_shares
the employee was set for. -
Ensure to provide error messages in your
require
statements.
-
-
After the
require
statements, add365 days
to theunlock_time
. This will calculate next year's unlock time before distributing this year's shares. We want to perform all of our calculations like this before distributing the shares. -
Next, set the new value for
distributed_shares
by calculating how many years have passed sincestart_time
multiplied byannual_distributions
. For example:-
The
distributed_shares
is equal to(now - start_time)
divided by365 days
, multiplied by the annual distribution. Ifnow - start_time
is less than365 days
, the output will be0
since the remainder will be discarded. If it is something like400
days, the output will equal1
, meaningdistributed_shares
would equal250
. -
Make sure to include the parenthesis around
now - start_time
in your calculation to ensure that the order of operations is followed properly.
-
-
The final
if
statement provided checks that in case the employee does not cash out until 5+ years after the contract start, the contract does not reward more than thetotal_shares
agreed upon in the contract.
-
-
Deploy and test your contract locally.
-
For this contract, test the timelock functionality by adding a new variable called
uint fakenow = now;
as the first line of the contract, then replace every other instance ofnow
withfakenow
. Utilize the followingfastforward
function to manipulatefakenow
during testing. -
Add this function to "fast forward" time by 100 days when the contract is deployed (requires setting up
fakenow
):function fastforward() public { fakenow += 100 days; }
-
Once satisfied with the contract's logic, revert the
fakenow
testing logic.
-
For some succinct and straightforward code snips, check out Solidity By Example
For a more extensive list of awesome Solidity resources, checkout Awesome Solidity
Another tutorial is available at EthereumDev.io
For building games, an excellent tutorial called CryptoZombies