

If you have been part of the Ethereum / Cryptocurrency ecosystem for at least a few months, you might be aware of the ICO craze going around.
Thereβs literally dozens of ICOs / Token Sales springing out every day as can be seen on popular platformsβββlike IcoAlert or TokenMarketβ tracking ICO activity.
One of the most common practices used by ICO teams to promote and incentivize their token sales is to offer hefty discounts or bonuses to contributors based on when they buy into their tokens or how much they are willing to invest.
Recently, thereβs been some hot debate whether or not itβs ok for ICOs to offer bonuses based on volume purchased / money spent.
Detractors of this practice argue that it goes against the spirit of EthereumβββAnd crypto in generalβββto reward people with more resources to gain an advantage over other people with less resources.
Volume-based bonuses wonβt be going away anytime soon. Soβ¦ How can we take advantage of these bonuses if we canβt (or wonβt) commit the minimum ether the ICO team is asking for?
One of the ways to do this would be to find a few friends that are also interested in contributing to a particular ICO and have them send their ether to one of the members of the group, have him buy the tokens and then distribute the acquired tokens accordingly among this group of friends.
In theory, this approach is pretty easy and straightforward, but in practice, it can become problematic. For one, you would have to trust this person receiving everyoneβs ether to come through, which already is a risky proposition. What if this person disappears with the money? What if he forgets his private key? What if his private key gets stolen? What if he makes a mistake and sends the money to an incorrect address? Thereβs so many things that could go wrong.
Also, what if we donβt have enough friends that are interested in investing with you in a particular ICO? You might still want to do it and pool the money with complete strangers. There are a few sites and forums where people can find other people to invest together, but this presents a huge risk for everyone involved. I wouldnβt send money blindly to someone I donβt know extremely well, and neither should you.
So, how can we collaboratively invest on an ICO without having to trust anyone else with our money? With a smart contract, of course! π
In the following paragraphs Iβm going to describe how to build a smart contract that allows several contributors to send money to it; have it invest in the selected ICO when some pre-conditions are met; and finally have it distribute the tokens purchased on a pro-rata basis.
This smart contract would act as an intermediary that collects the money from several accounts and then forwards the collected funds to an ICO contract in one transaction in order to gain access to the volume-based bonus offered by the team.
The tokens would then be bought by the contract, which allows the contributors that participated in the investment pool to withdraw them to their own accounts.
Letβs take a look at the smart contract that allows all this.
You can find the complete source code in my Github repository. Please take a look at the README.md file for further usage instructions, more in-depth information and examples.
The ICOPool contract has 4 parts to it:
In order to put an investment pool in place, first we have to deploy the contract with a few parameters. The account deploying the ICOPool contract will become its admin. The adminβs only purpose is to execute the function that buys into the ICO.
Hereβs the code involved in the creation of the investment pool.
function ICOPool(address _targetICO,
uint _minContribution,
uint _maxContribution,
uint _poolSoftCap,
uint _poolHardCap,
uint _contributorsSoftCap,
uint _contributorsHardCap
) public {
require (_targetICO != address(0));
require (_minContribution > 0);
require (_maxContribution > _minContribution);
require (_poolHardCap > _poolSoftCap);
require (_contributorsSoftCap > 1);
require (_contributorsHardCap >= _contributorsSoftCap);
// Max people * their minimum contribution should be able to meet pool softcap
// For example, we can't allow having the max contributors (10 people) put $100 each when the softcap is $1500.
require(_contributorsHardCap * _minContribution >= _poolSoftCap);
// Min people * their maximum contribution should be within pool hardcap
// For example, we can't allow 3 people to reach the hardcap if the minimum contributors is 5
require(_contributorsSoftCap * _maxContribution <= _poolHardCap);
targetICO = _targetICO;
poolAdmin = msg.sender;
minContribution = _minContribution;
maxContribution = _maxContribution;
poolSoftCap = _poolSoftCap;
poolHardCap = _poolHardCap;
contributorsSoftCap = _contributorsSoftCap;
contributorsHardCap = _contributorsHardCap;
}
The contract receives quite a few constructor parameters:
Notice thereβs a complex relationship between all these parameters so we donβt risk the chance of the investment pool getting stuck:
Itβs also extremely important to notice that the ICOPool contract has no way to know what the minimum contribution is in order to trigger the bonuses. Itβs up to the admin to correctly set the soft and hard caps accordingly.
Once the investment pool is set up, the person who created the contract can start promoting it among other people to pool contributions.
Sending ether to the investment pool is done the same way as one would with a crowdsale. Just transfer the ether to the given address and the contract will take care of it.
*Itβs worth mentioning that the contributor should take the same measures he takes when investing directly on the crowdsale. For example, they must send the money from an account they own the private key of.
Hereβs the code that gets executed when someone sends money from their account.
function() payable public {
require(msg.value > 0);
require(msg.value >= minContribution && msg.value <= maxContribution); // Must send eth within min and max contributions
require(contributorsBalance[msg.sender].add(msg.value) <= maxContribution); // msg.sender's balance can't exceed max contribution limit
// Pool can't exceed hard cap
require(poolBalance.add(msg.value) <= poolHardCap);
//Register how much eth the pool has
poolBalance = poolBalance.add(msg.value);
//If it is the first time this account contributes, increase num. of contributors
if (contributorsBalance[msg.sender] == 0){
amountOfContributors++;
}
// Pool can't exceed contributors hard cap
require(amountOfContributors <= contributorsHardCap);
//Register how much eth has each contributor put into the pool
contributorsBalance[msg.sender] = contributorsBalance[msg.sender].add(msg.value);
}
First, we make sure that the contribution being made is within the boundaries the admin defined and that the people/money hard cap is not exceeded.
If the contribution is accepted, we add the money sent to the contributorβs balance. The contributor doesnβt have to put all the money in one go, but each contribution should meet the minimum set by the admin.
At any point in timeβββas long as the minimum requirements are metβββ, the admin may decide to stop collecting funds and make the purchase of the tokens from the ICO.
Once he does, the investment pool contract may no longer accept incoming transfers of ether.
As I mentioned before, this step must be performed by the admin, although the contract could be modified to allow any of the contributors to do it. This other approach makes the contract less dependent on one person, but it also makes it more messy and prone to problems. The main issue with allowing anyone to buy the tokens has to do with the fact that this ICOPool contract has no way to know the inner workings of the target ICO. In order to avoid conflict, the admin is the one that decides when to buy the tokens, and responsible for it. For example, the contract has no way to check if the volume bonus promoted by the ICO team is active at the moment, so itβs up to the pool admin to check that and move forward with the token purchase.βββOr not doing anything so the contributors can withdraw their fundsβββ.
function buyTokensFromICO() public {
require(!investedInICO);
require(poolBalance >= poolSoftCap);
require(amountOfContributors >= contributorsSoftCap);
require(this.balance >= poolBalance);
//Can be called only by the pool admin to avoid timing problems
// We'll need to trust the admin to execute this at the right moment
// Could be changed to allow any contributor to call it.
require(msg.sender == poolAdmin);
investedInICO = true;
// BE CAREFUL, OPENING RE-ENTRANCY DOOR
require(targetICO.call.value(poolBalance)());
// **************
//If you are hesitant about using call() you can instead instantiate
//the target ICO and directly use whatever function it has to buy tokens
// ------
//Crowdsale c = Crowdsale(targetICO);
//c.buyTokens.value(poolBalance)();
// **************
}
Sending the funds to the target ICO is pretty straightforward. Once we have checked that the soft caps have been met, we proceed to forward the ether to the target ICO. Using the call() function and sending the pool balance along will trigger the ICOβs fallback function.
Once thing the admin (and contributors) should do before investing in the pool is making sure the pool will indeed be able to invest in the target ICO. This contract assumes the ICO has a fallback function that triggers the contractβs own buyTokens (or whatever it is called) function.
If the target ICO doesnβt have a fallback function implemented or you feel insecure about using the call() function to execute potentially insecure code, then you could use another method Iβve left commented in the code. You could instantiate the target ICO contract and call itβs buyTokens function directly.
This function also sets the investedInICO flag so we can only call this function once. If the people involved wanted to pool more money and invest a second time, they would have to create a new ICOPool contract.
Once the previous step has been completed, the target ICOβs token should be holding our ICOPoolβs token balance.
You should be able to check this by calling the balanceOf() function of the token by passing the ICOPoolβs address to it.
So, thereβs one more step left that each contributor has to complete; the withdrawal of the tokens each one is entitled to.
function withdrawTokens(address _tokenAddress) public {
require(contributorsBalance[msg.sender] > 0);
ERC20 token = ERC20(_tokenAddress);
require (token.balanceOf(this) > 0);
// tokenBalance is always the max tokens the pool bought (balanceOf + already withdrawn)
uint tokenBalance = token.balanceOf(this).add(tokensWithdrawn);
// Get contributor share based on his contribution vs total pool
// poolBalance (total wei pooled) -> contributorsBalance[msg.sender] (wei put by msg.sender)
// tokenBalance (total tokens bought with poolBalance) -> tokensToWithdraw (how many tokens corresponds to msg.sender)
uint tokensToWithdraw = tokenBalance.mul(contributorsBalance[msg.sender]).div(poolBalance);
tokensToWithdraw = tokensToWithdraw.sub(tokensWithdrawnByContributor[msg.sender]);
require(tokensToWithdraw > 0);
// Keep track of tokens already withdrawn
tokensWithdrawn = tokensWithdrawn.add(tokensToWithdraw);
tokensWithdrawnByContributor[msg.sender] = tokensWithdrawnByContributor[msg.sender].add(tokensToWithdraw);
// Transfer calculated tokens to msg.sender
require(token.transfer(msg.sender,tokensToWithdraw));
}
As I mentioned above, the ICOPool contract is now holding the tokens it bought. Now itβs up to each contributor to withdraw their own tokens by having each one of them call the withdrawTokens(address _tokenAddress) function.
Notice the function requires one parameter, the tokenβs address, not to be confused by the ICOβs address. At this point, the ICO has no utility, what we need is to change the ownership of the tokens, so we need to get a hold of the token address.
When one of the contributors calls this function, it will calculate how many tokens they are entitled to based on the total money the pool received and how much this personβs contribution represents. For example, suppose the total pool balance was 100 eth, that were used to buy 100.000 tokens. If I invested 30 ether, then I would be entitled to 30.000 tokens.
The function will figure out how many tokens of the total that were bought correspond to the contributor and then call the transfer function on the token to make the transfer from the contract to the contributor.
If you call balanceOf() again, the contributor should now own those many tokens and the contract should have those many less.
One thing to notice is that this function has been designed so that the contributor may call it more than once, and if there were more tokens than before, he would get the difference. You might be wondering why we are doing this if I previously mentioned that the ICOPool contract may only buy tokens once. Well, thereβs the slight chance that our ICOPool contract might receive tokens besides buying them. Thereβs a popular marketing practice some ICOs are implementing lately called Airdrops where they basically send tokens to random addresses in order to promote their product.
The way our withdrawTokens() function works allows the contributors to get their share of these tokens as well.
There are lots of improvements that can be done to this contract. For example:
Drop me a line if you are interested in implementing this contract to invest in an ICO. And please do contact me if you find any bugs or you want to discuss any parts of this code.
I hope you enjoyed reading this article as much as I enjoyed writing it. Iβm currently taking consultancy jobs related to smart contracts development. If you are planning on raising funds through an ICO or building a Blockchain-based product, feel free to get in touch with me.
Create your free account to unlock your custom reading experience.