Ganesh oli
Posted on March 30, 2023
Donation smart contract is useful when you are learning substrate based ink! smart contract. This contract is based on ink! v4.0.0
and use Vec and Mapping data structure. Vec(i.e contiguous growable array type) is used to store and return Donation list and Mapping (i.e key-value pairs directly into contract storage) which is used to store Donation (i.e value)
with respect to donation_id (i.e key)
. There are also other data structure like AccountId (i.e 32-byte type)
, u128 (to store balance) and i32 (to store donation id).
Through this contract you can:
- Initialize beneficiary account while contract deployment.
- Can change beneficiary at any time.
- Donate the beneficiary address.
- Can view donation list.
Before start writing ink! smart contract
, you need to setup rust
, cargo
and cargo contract
(i.e Setup and deployment tool for developing Wasm based smart contracts via ink!). You can setup rust and cargo from here and can install latest cargo contract from here.
After successfully setup all requirement, you can create ink! smart contract using following command.
cargo contract new <contract_name>
contract_name must not start with alphabetic character and contains `alphanumeric characters and underscores`
Successfully create contract can be build with following command
cargo contract build
if you wan't to build in release, then add --release flag
Replace, your #[ink(storage)]
Macros with the below code snippet.
owner
: Contract Address Owner
beneficiary
: Beneficiary account where you can donate balance.
donations
: mapping to storage donation data.
donation_id
: id for donation
#[ink(storage)]
pub struct DonationContract {
owner: AccountId,
beneficiary: AccountId,
donations: Mapping<DonationId, Donation>,
donation_id: i32,
}
Donation
is Custom Data Structures, we have to define separately above the #[ink(storage)]
macro. Derive traits Decode and Encode provide a way to serialize and deserialize rust data type into complete binary format, you can learn more from here. This data structure defines two fields:
account
: AccountId, user who donate.
amount
: u128, user how much he donate.
#[derive(scale::Decode, scale::Encode)]
#[cfg_attr(
feature = "std",
derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
)]
pub struct Donation {
account: AccountId,
amount: u128,
}
Since, ink!v4.0.0
remove AccountId default implementation, we can't use Default
macro inside derive, So we have to define separately just below Donation struct.
// Donation struct default implementations
impl Default for Donation {
fn default() -> Self {
Self {
account: zero_address(),
amount: 0,
}
}
}
We defined zero_address()
function, separately, which you can define like this:
/// Helper for referencing the zero address (`0x00`). Note that in practice this address should
/// not be treated in any special way (such as a default placeholder) since it has a known
/// private key.
fn zero_address() -> AccountId {
[0u8; 32].into()
}
Every ink! contract has one or more constructor. Our constructor is defined below. You can include this code in your #[ink(constructor)]
macro. If you want to learn more click here.
Self::env().caller()
gives owner of the contract. The owner will be whoever deploy the contract.
Return all the fields from #[ink(storage)]
macro like:
- owner
- beneficiary which is provided while contract instantiation.
- donations mapping we can define like
Mapping::default()
- donation_id can be initialize with 1 or
Default::default()
#[ink(constructor)]
pub fn new(beneficiary: AccountId) -> Self {
let owner = Self::env().caller();
Self {
owner,
beneficiary,
donations: Mapping::default(),
donation_id: 1,
}
}
Normal user can view beneficiary address anytime. Only owner of the contract can change the beneficiary address. These are public function which can be represent by #[ink(message)]
macro, then only you can view those api while interaction with frontend. Contract must contains at least one or many #[ink(message)]
, you can get more info from here
#[ink(message)]
pub fn get_beneficiary(&self) -> Option<AccountId> {
Some(self.beneficiary)
}
#[ink(message)]
pub fn change_beneficiary(&mut self, new_beneficiary: AccountId) {
let caller = self.env().caller();
assert!(
self.owner == caller,
"Only owner can change the beneficiary account"
);
self.beneficiary = new_beneficiary;
}
assert!()
macro check whether caller of the function is owner or not. If not owner then, he can't change the new beneficiary address. In order to update value we call self
and mut so that beneficiary from storage can be updated easily, as you might be aware about rust.
Add another function which is also public below.
#[ink(message, payable)]
pub fn donation(&mut self) {
// Account who is donating
let caller = self.env().caller();
let donation_id = self.next_donation_id();
// Donation amount
let donation_amount = self.env().transferred_value();
let mut donated_so_far = self.donations.get(donation_id).unwrap_or_default();
assert!(donation_amount > 0, "Cannot transfer 0 donation");
// Total donation amount so far by caller
donated_so_far.amount += donation_amount;
donated_so_far.account = caller;
// Insert total donation amount with respect to caller
self.donations.insert(donation_id, &donated_so_far);
// Send donation amount to the beneficiary account
self.env()
.transfer(self.beneficiary, donation_amount)
.unwrap_or_default();
}
This is function is payable
, which means it allow receiving value. You can deep dive with this macro from here.
- caller who is calling this function while donating amount.
- donation_id is local variable which store id from
next_donation_id()
function which is private function and defined below.
pub fn next_donation_id(&mut self) -> DonationId {
let id = self.donation_id;
self.donation_id += 1;
id
}
}
-
self.env().transferred_value()
returns the value for contract execution more info here.-
donated_so_far
variable which storage donation so far by account.
-
If donation more than 0, then donation store in storage.
In this way wasm based ink! smart contract can be written. This is way basic contract for beginner level. You can find all the code in my git hub account. I haven't included here but you can find unit test of this contract as well.
Posted on March 30, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.