Mintlock
#![no_std]
use soroban_sdk::{
contract, contractimpl, contractclient, contracterror, contracttype, Address, Env, IntoVal, log
};
#[allow(dead_code)]
#[contractclient(name = "MintClient")]
trait MintInterface {
fn mint(env: Env, to: Address, amount: i128);
}
#[contracterror]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum Error {
NotAuthorizedMinter = 1,
DailyLimitInsufficient = 2,
NegativeAmount = 3,
}
#[contracttype]
pub enum StorageKey {
/// Admin. Value is an Address.
Admin,
/// Minters are stored, keyed by the contract and minter
/// addresses. Value is a MinterConfig.
Minter(Address, Address),
/// Minters stats are stored, keyed by the contract and minter
/// addresses, epoch length, and epoch, which is the ledger
/// number divided by the number of ledgers in the epoch.
/// Value is a MinterStats.
MinterStats(Address, Address, u32, u32),
}
#[contracttype]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MinterConfig {
limit: i128,
epoch_length: u32,
}
#[contracttype]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct MinterStats {
consumed_limit: i128,
}
#[contract]
pub struct Contract;
#[contractimpl]
impl Contract {
/// Set the admin.
pub fn set_admin(env: Env, new_admin: Address) {
if let Some(admin) = env
.storage()
.instance()
.get::<_, Address>(&StorageKey::Admin)
{
/// @dev should remove logs before deploying smart contracts
log!(&env, "Admin address: {}", admin);
admin.require_auth();
};
/// @dev should remove logs before deploying smart contracts
log!(&env, "Admin address: {}", new_admin);
env.storage().instance().set(&StorageKey::Admin, &new_admin);
}
/// Return the admin address.
pub fn admin(env: Env) -> Address {
env.storage()
.instance()
.get::<_, Address>(&StorageKey::Admin)
.unwrap()
}
/// Set the config for a minter for the given contract. Requires admin authentication.
pub fn set_minter(env: Env, contract: Address, minter: Address, config: MinterConfig) {
Self::admin(env.clone()).require_auth();
env.storage()
.persistent()
.set(&StorageKey::Minter(contract, minter), &config);
}
/// Returns the config, current epoch, and current epoch stats for a minter.
pub fn minter(
env: Env,
contract: Address,
minter: Address,
) -> Result<(MinterConfig, u32, MinterStats), Error> {
// The config value
let config = env
.storage()
.persistent()
.get::<_, MinterConfig>(&StorageKey::Minter(contract.clone(), minter.clone()))
.ok_or(Error::NotAuthorizedMinter)?;
// The current epoch value
let epoch = env.ledger().sequence() / config.epoch_length;
// The current epoch's stats
let stats = env
.storage()
.temporary()
.get::<_, MinterStats>(&StorageKey::MinterStats(
contract.clone(),
minter.clone(),
config.epoch_length,
epoch,
))
.unwrap_or_default();
/// @dev should remove logs before deploying smart contracts
log!(&env, "Minter stats: {}, Config: {}, Epoch: {}", stats, config, epoch);
Ok((config, epoch, stats))
}
/// Calls the 'mint' function of the contract with 'to' and 'amount'.
/// Authorized by the 'minter'. Uses some of the authorized minter's current epoch's limit.
pub fn mint(
env: Env,
contract: Address,
minter: Address,
to: Address,
amount: i128,
) -> Result<(), Error> {
// Verify minter is authenticated, by providing authorizing args.
minter.require_auth_for_args((&contract, &to, amount).into_val(&env));
// Verify amount is positive.
if amount < 0 {
return Err(Error::NegativeAmount);
}
// Verify minter is authorized by contract.
let admin = Self::admin(env.clone());
if admin != minter {
let Some(admin) = env
.storage()
.persistent()
.get::<_, MinterConfig>(&StorageKey::Minter(contract.clone(), minter.clone()))
else {
return Err(Error::NotAuthorizedMinter);
};
// Check and track daily limit.
let config = env
.storage()
.persistent()
.get::<_, MinterConfig>(&StorageKey::Minter(contract.clone(), minter.clone()))
.ok_or(Error::NotAuthorizedMinter)?;
let epoch = env.ledger().sequence() / config.epoch_length;
let minter_stats_key = StorageKey::MinterStats(
contract.clone(),
minter.clone(),
config.epoch_length,
epoch,
);
let minter_stats = env
.storage()
.temporary()
.get::<_, MinterStats>(&minter_stats_key)
.unwrap_or_default();
/// @dev should remove logs before deploying smart contracts
log!(&env, "Minter stats: {}, Config: {}, Epoch: {}", minter_stats, config, epoch);
let new_minter_stats = MinterStats {
consumed_limit: minter_stats.consumed_limit + amount,
};
if new_minter_stats.consumed_limit > config.limit {
return Err(Error::DailyLimitInsufficient);
};
env.storage()
.temporary()
.set::<_, MinterStats>(&minter_stats_key, &new_minter_stats);
env.storage()
.temporary()
.extend_ttl(&minter_stats_key, 0, epoch * config.epoch_length);
}
// Perform the mint.
let client = MintClient::new(&env, &contract);
client.mint(&to, &amount);
Ok(())
}
}
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.
MintInterface
(marked with #[allow(dead_code)]
): This defines an interface for a possible external contract named "MintClient" that can be called for minting functionality.
Error
enum: Defines different error codes for the contract, like NotAuthorizedMinter
, DailyLimitInsufficient
, and NegativeAmount
.
StorageKey
enum: Defines different keys used to access data in the contract's storage. These include Admin
to store the admin address, Minter(contract, minter)
to store a minter's configuration for a specific contract, and MinterStats(contract, minter, epoch_length, epoch)
to store a minter's stats for a specific epoch.
MinterConfig
Defines the configuration for a minter, including their daily limit and the epoch length for tracking usage.
MinterStats
Stores the amount a minter has minted within the current epoch.
set_admin
This function sets the contract's admin address. Only the current admin can call this function.
admin
This function returns the current admin address.
set_minter
This function sets the configuration for a minter for a specific contract. Only the admin can call this function.
minter
This function retrieves the configuration, current epoch, and current epoch stats for a specific minter.
mint
This is the core function for minting. It allows authorized minters to mint a specific amount for a recipient.
Run in Playground
Loading playground...