Skip to content

Commit

Permalink
feat(erc20): Add ERC20Capped to an Example (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
bidzyyys committed May 15, 2024
1 parent a6888c1 commit 8f0431c
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 6 deletions.
84 changes: 83 additions & 1 deletion contracts/src/erc20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,36 @@ impl ERC20 {
Ok(())
}

/// Creates a `value` amount of tokens and assigns them to `account`,
/// by transferring it from `Address::ZERO`.
///
/// Relies on the `_update` mechanism
///
/// # Panics
///
/// If `_total_supply` exceeds `U256::MAX`.
///
/// # Errors
///
/// If the `account` address is `Address::ZERO`, then the error
/// [`Error::InvalidReceiver`] is returned.
///
/// # Events
///
/// Emits a [`Transfer`] event.
pub fn _mint(
&mut self,
account: Address,
value: U256,
) -> Result<(), Error> {
if account.is_zero() {
return Err(Error::InvalidReceiver(ERC20InvalidReceiver {
receiver: Address::ZERO,
}));
}
self._update(Address::ZERO, account, value)
}

/// Transfers a `value` amount of tokens from `from` to `to`,
/// or alternatively mints (or burns)
/// if `from` (or `to`) is the zero address.
Expand Down Expand Up @@ -502,7 +532,7 @@ mod tests {
}

#[grip::test]
#[should_panic]
#[should_panic = "Should not exceed `U256::MAX` for `_total_supply`"]
fn update_mint_errors_arithmetic_overflow(contract: ERC20) {
let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
let one = U256::from(1);
Expand All @@ -517,6 +547,58 @@ mod tests {
let _result = contract._update(Address::ZERO, alice, one);
}

#[grip::test]
fn mint_works(contract: ERC20) {
let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
let one = U256::from(1);

// Store initial balance & supply
let initial_balance = contract.balance_of(alice);
let initial_supply = contract.total_supply();

// Mint action should work
let result = contract._mint(alice, one);
assert!(result.is_ok());

// Check updated balance & supply
assert_eq!(initial_balance + one, contract.balance_of(alice));
assert_eq!(initial_supply + one, contract.total_supply());
}

#[grip::test]
fn mint_errors_invalid_receiver(contract: ERC20) {
let receiver = Address::ZERO;
let one = U256::from(1);

// Store initial balance & supply
let initial_balance = contract.balance_of(receiver);
let initial_supply = contract.total_supply();

// Mint action should work
let result = contract._mint(receiver, one);
assert!(matches!(result, Err(Error::InvalidReceiver(_))));

// Check updated balance & supply
assert_eq!(initial_balance, contract.balance_of(receiver));
assert_eq!(initial_supply, contract.total_supply());
}

#[grip::test]
#[should_panic = "Should not exceed `U256::MAX` for `_total_supply`"]
fn mint_errors_arithmetic_overflow(contract: ERC20) {
let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
let one = U256::from(1);
assert_eq!(U256::ZERO, contract.balance_of(alice));
assert_eq!(U256::ZERO, contract.total_supply());

// Initialize state for the test case -- Alice's balance as U256::MAX
contract
._update(Address::ZERO, alice, U256::MAX)
.expect("ERC20::_update should work");
// Mint action should NOT work -- overflow on `_total_supply`.
let _result = contract._mint(alice, one);
}

#[grip::test]
fn update_burn(contract: ERC20) {
let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/utils/capped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl Capped {
}

/// Returns the cap on the token's total supply.
fn cap(&self) -> U256 {
pub fn cap(&self) -> U256 {
self._cap.get()
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/erc20/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
version = "0.0.0"

[dependencies]
contracts = { path = "../../contracts", features = ["erc20_metadata", "erc20_burnable"] }
contracts = { path = "../../contracts", features = ["erc20_metadata", "erc20_burnable", "erc20_capped"] }
alloy-primitives.workspace = true
stylus-sdk.workspace = true
stylus-proc.workspace = true
Expand Down
43 changes: 40 additions & 3 deletions examples/erc20/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#![cfg_attr(not(test), no_main, no_std)]
extern crate alloc;

use alloc::string::String;
use alloc::{string::String, vec::Vec};

use alloy_primitives::{Address, U256};
use contracts::{
erc20::{extensions::ERC20Metadata, ERC20},
erc20_burnable_impl,
utils::Capped,
};
use stylus_sdk::prelude::{entrypoint, external, sol_storage};

Expand All @@ -18,18 +20,31 @@ sol_storage! {
ERC20 erc20;
#[borrow]
ERC20Metadata metadata;
#[borrow]
Capped capped;
}
}

#[external]
#[inherit(ERC20, ERC20Metadata)]
#[inherit(ERC20, ERC20Metadata, Capped)]
impl Token {
// This macro implements ERC20Burnable functions -- `burn` and `burn_from`.
// Expects an `ERC20 erc20` as a field of `Token`.
erc20_burnable_impl!();

pub fn constructor(&mut self, name: String, symbol: String) {
// We need to properly initialize all Token's attributes.
// For that we need to call each attributes' constructor if exists.
//
// NOTE: This is a temporary solution for state initialization.
pub fn constructor(
&mut self,
name: String,
symbol: String,
cap: U256,
) -> Result<(), Vec<u8>> {
self.metadata.constructor(name, symbol);
self.capped.constructor(cap)?;
Ok(())
}

// Overrides the default [`Metadata::decimals`], and sets it to `10`.
Expand All @@ -39,4 +54,26 @@ impl Token {
pub fn decimals(&self) -> u8 {
DECIMALS
}

// Add token minting feature.
// Make sure to handle `Capped` properly.
pub fn mint(
&mut self,
account: Address,
value: U256,
) -> Result<(), Vec<u8>> {
let max_supply = self.capped.cap();
let supply = self.erc20.total_supply() + value;
if supply > max_supply {
return Err(contracts::utils::capped::Error::ExceededCap(
contracts::utils::capped::ExceededCap {
increased_supply: supply,
cap: max_supply,
},
))?;
}

self.erc20._mint(account, value)?;
Ok(())
}
}

0 comments on commit 8f0431c

Please sign in to comment.