Token Donations
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, Address, Val, TryFromVal, ConversionError, token, log};
#[derive(Clone, Copy)]
pub enum DataKey {
AcceptedToken = 0, // address of the accepted token
DonationsRecipient = 1 // address of the donations recipient
}
impl TryFromVal<Env, DataKey> for Val {
type Error = ConversionError;
fn try_from_val(_env: &Env, v: &DataKey) -> Result<Self, Self::Error> {
Ok((*v as u32).into())
}
}
// Helper functions
fn put_token_address(e: &Env, token: &Address) {
e.storage().instance().set(&DataKey::AcceptedToken, token);
}
fn put_donations_recipient(e: &Env, recipient: &Address) {
e.storage().instance().set(&DataKey::DonationsRecipient, recipient);
}
fn get_token_address(e: &Env) -> Address {
e.storage()
.instance()
.get(&DataKey::AcceptedToken)
.expect("not initialized")
}
fn get_donations_recipient(e: &Env) -> Address {
e.storage()
.instance()
.get(&DataKey::DonationsRecipient)
.expect("not initialized")
}
fn get_balance(e: &Env, token_address: &Address) -> i128 {
let client = token::Client::new(e, token_address);
client.balance(&e.current_contract_address())
}
// Transfer tokens from the contract to the recipient
fn transfer(e: &Env, to: &Address, amount: &i128) {
let token_contract_address = &get_token_address(e);
let client = token::Client::new(e, token_contract_address);
client.transfer(&e.current_contract_address(), to, amount);
}
// Contract Trait
pub trait DonationsTrait {
// Sets the recipient address and the token that will be accepted as a donation
fn initialize(e: Env, recipient: Address, token: Address);
// Donates specified amount units of the accepted token
fn donate(e: Env, donor: Address, amount: i128);
// Transfers all the donated amounts to the recipient. Can be called by anyone.
fn withdraw(e: Env);
// Get the token address that is accepted for donations.
fn token(e: Env) -> Address;
// Get the donations recipient address.
fn recipient(e: Env) -> Address;
}
#[contract]
struct Donations;
#[contractimpl]
impl DonationsTrait for Donations {
// Sets the recipient address and the token that will be accepted as a donation
fn initialize(e: Env, recipient: Address, token: Address) {
assert!(
!e.storage().instance().has(&DataKey::AcceptedToken),
"already initialized"
);
put_token_address(&e, &token);
put_donations_recipient(&e, &recipient);
}
// Donates amount units of the accepted token
fn donate(e: Env, donor: Address, amount: i128) {
donor.require_auth();
let token_address = get_token_address(&e);
let client = token::Client::new(&e, &token_address);
client.transfer(&donor, &e.current_contract_address(), &amount);
}
// Transfer all donation amounts to recipient. Can be called by anyone.
fn withdraw(e: Env) {
let token = get_token_address(&e);
let recipient = get_donations_recipient(&e);
transfer(&e, &recipient, &get_balance(&e, &token));
/// @dev should remove logs before deploying smart contracts
log!(&e, "Token: {}, Recipient: {}", token, recipient);
}
// Get the token address that is accepted as donations
fn token(e: Env) -> Address {
get_token_address(&e)
}
// Get the donations recipient address
fn recipient(e: Env) -> Address {
get_donations_recipient(&e)
}
}
Explanation
#![no_std]
This attribute prevents linking to the standard library, making the code lighter and more efficient for Soroban contracts. It's big so we save on size.
use soroban_sdk::{contract, contractimpl, Env, log}
Imports stuffs from the Soroban SDK. Env
is basic Soroban type, we need it because we can't use the Rust standard library.
pub
Marks a function as external, meaning it can be invoked outside of the context of the contract code itself.
impl
Defines trait implementations, which specify how a type conforms to a particular trait.
TryFromVal
Converts values from one type to another, potentially returning an error if the conversion is not possible.
ConversionError
Will likely be defined elsewhere in the code and represents the possible errors that can occur during the conversion. If no errors the Ok
keyword will return the appropriate response.
into()
This method is used to convert the u32
value to a Val
value, assuming that the Val
type has an implementation of the From<u32>
trait.
fn
This keyword is used to define functions in Rust.
trait
This keyword defines interfaces in Rust, which specify a set of methods and associated functions that a type must implement to conform to the trait.
#[contract]
Marks the struct as a Soroban smart contract. Soroban smart contracts are defined as Rust structs.
#[contractimpl]
Marks the implementation block as containing contract methods and transforms it to code that Soroban can evaluate directly.
initialize
Sets the recipient address and the token that will be accepted as a donation.
donate
Donates specifiied amount units of the accepted token.
withdraw
Transfers all donation amounts to recipient. Can be called by anyone; this can be noted by the fact that the transfer()
method is not connected to an initialized client
mechanism.
token
and recipient
each retrieve Address values as identified by their respective names.
Run in Playground
Loading playground...