Crowdsale (ICO)
Introduction
A public offering to invest in a brand-new cryptocurrency or other digital asset is known as a cryptocurrency Crowdsale. An initial coin offering (ICO) can be used by new projects to raise money for development and other purposes. ICO is a time-limited campaign where investors can exchange their cryptocurrencies defined in the campaign to newly proposed tokens. The new tokens are promoted as future functional units after the ICO's funding goal is met and the project launches.
An example of an ICO smart-contract implementation described in this article is one of many other decentralized applications that can be implemented and laucnhed on Gear. This article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own Crowdsale ICO application and run it on the Gear Network.
Initial funds with which a token is purchased are determined by the Gear fungible tokens contract - gFT. The contract's source code is available on GitHub.
Interface
Source files
messages.rs
- contains function of the fungible token contract. Crowdsale contract interacts with fungible token contract through transfer_tokens function:
pub async fn transfer_tokens(
token_id: &ActorId, // - the fungible token contract address
from: &ActorId, // - the sender address
to: &ActorId, // - the recipient address
amount: u128, // - the amount of tokens
)
This function sends a message (the action is defined in the enum IcoAction) and gets a reply (the reply is defined in the enum IcoEvent):
let _transfer_response = msg::send_for_reply(
*token_id,
FTAction::Transfer {
from: *from,
to: *to,
amount,
},
0,
)
.expect("Error in message")
.await
.expect("Error in transfer");
asserts.rs
- contains asserts functions:owner_message
andnot_zero_address
.
owner_message
checks ifmsg::source()
is equal toowner
. Otherwise, it panics:
pub fn owner_message(owner: &ActorId, message: &str) {
if msg::source() != *owner {
panic!("{}: Not owner message", message)
}
}
not_zero_address
checks ifaddress
is not equal toZERO_ID
. Otherwise, it panics:
pub fn not_zero_address(address: &ActorId, message: &str) {
if address == &ZERO_ID {
panic!("{}: Zero address", message)
}
}
lib.rs
- defines the contract logic.
Structs
The contract has the following structs:
struct IcoContract {
ico_state: IcoState,
start_price: u128,
price_increase_step: u128,
time_increase_step: u128,
tokens_sold: u128,
tokens_goal: u128,
owner: ActorId,
token_address: ActorId,
token_holders: BTreeMap<ActorId, u128>,
}
where:
ico_state
isIcoState
struct which consists of:
pub struct IcoState {
pub ico_started: bool, // true if ICO was started
pub start_time: u64, // time when ICO was started, otherwise is zero
pub duration: u64, // duration of the ICO, otherwise is zero
pub ico_ended: bool, // true if ICO was ended
}
start_price
- initial price of tokensprice_increase_step
- how much does the price increasetime_increase_step
- the period of time after which the price increasestokens_sold
- how many tokens were soldtokens_goal
- how many tokens are we going to sellowner
- contract ownertoken_address
- fungible token addresstoken_holders
- the list of buyers and the number of tokens they bought
Functions
- Starts the ICO. Only owner can call it:
async fn start_ico(&mut self, config: IcoAction)
replies with:
IcoEvent::SaleStarted {
duration,
start_price,
tokens_goal,
price_increase_step,
time_increase_step,
},
- Purchase of tokens. Anyone with enough balance can call and buy tokens:
pub fn buy_tokens(&mut self, tokens_cnt: u128)
replies with:
IcoEvent::Bought {
buyer,
amount,
change,
}
- Ends the ICO. Only owner can call it:
async fn end_sale(&mut self)
replies with:
IcoEvent::SaleEnded
Conclusion
The source code of this example of ICO smart contract and the example of an implementation of its testing is available on Github.
For more details about testing smart contracts written on Gear, refer to the Program Testing article.