From 59dd8f7ede17f250326736c50682576dd92c42a2 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 4 Apr 2024 11:48:05 +0200 Subject: [PATCH] feat(erc20): implement Burnable extension (#27) --- contracts/Cargo.toml | 5 +- contracts/src/erc20/extensions/burnable.rs | 60 ++++++++++++++++++++++ contracts/src/erc20/extensions/mod.rs | 3 ++ contracts/src/erc20/mod.rs | 44 ++++++++++++++-- docs/modules/ROOT/nav.adoc | 1 - 5 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 contracts/src/erc20/extensions/burnable.rs diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 92851e04b..c91b8bb0e 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -23,6 +23,7 @@ wavm-shims = { path = "../lib/wavm-shims" } default = [] erc20 = [] erc20_metadata = ["erc20"] +erc20_burnable = ["erc20"] erc721 = [] [lib] @@ -42,9 +43,9 @@ erc721 = [] # SDK. We don't set `lib` here because our contracts are meant to be used as an # addition to other contracts. For this use case, the abi of those contracts # will contain ours. -# +# # The trade-off is being able to run `cargo test` with conditional compilation # vs being able to run `cargo stylus export-abi`. -# +# # This may change in the future, so this behavior should not be relied upon. crate-type = ["cdylib"] diff --git a/contracts/src/erc20/extensions/burnable.rs b/contracts/src/erc20/extensions/burnable.rs new file mode 100644 index 000000000..33840c969 --- /dev/null +++ b/contracts/src/erc20/extensions/burnable.rs @@ -0,0 +1,60 @@ +use stylus_sdk::{ + alloy_primitives::{Address, U256}, + msg, + prelude::*, +}; + +use crate::erc20::{Error, ERC20}; + +sol_storage! { + pub struct ERC20Burnable { + ERC20 erc20; + } +} + +#[external] +#[inherit(ERC20)] +impl ERC20Burnable { + /// Destroys a `value` amount of tokens from the caller. + /// lowering the total supply. + /// + /// Relies on the `update` mechanism. + /// + /// # Arguments + /// + /// * `value` - Amount to be burnt. + /// + /// If the `from` address doesn't have enough tokens, then the error + /// [`Error::InsufficientBalance`] is returned. + pub fn burn(&mut self, value: U256) -> Result<(), Error> { + self.erc20._burn(msg::sender(), value) + } + + /// Destroys a `value` amount of tokens from `account`, + /// lowering the total supply. + /// + /// Relies on the `update` mechanism. + /// + /// # Arguments + /// + /// * `account` - Owner's address. + /// * `value` - Amount to be burnt. + /// + /// If not enough allowance is available, then the error + /// [`Error::InsufficientAllowance`] is returned. + /// * If the `from` address is `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// If the `from` address doesn't have enough tokens, then the error + /// [`Error::InsufficientBalance`] is returned. + pub fn burn_from( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Error> { + self.erc20._spend_allowance(account, msg::sender(), value)?; + self.erc20._burn(account, value) + } +} + +#[cfg(test)] +mod tests {} diff --git a/contracts/src/erc20/extensions/mod.rs b/contracts/src/erc20/extensions/mod.rs index 4638989af..4eb875e72 100644 --- a/contracts/src/erc20/extensions/mod.rs +++ b/contracts/src/erc20/extensions/mod.rs @@ -1,2 +1,5 @@ #[cfg(any(test, erc20_metadata))] pub mod metadata; + +#[cfg(any(test, erc20_burnable))] +pub mod burnable; diff --git a/contracts/src/erc20/mod.rs b/contracts/src/erc20/mod.rs index 700d03bad..8c931c635 100644 --- a/contracts/src/erc20/mod.rs +++ b/contracts/src/erc20/mod.rs @@ -62,7 +62,7 @@ sol! { #[derive(Debug)] error ERC20InvalidSpender(address spender); - /// Indicates a failure where an overflow error in math occured. Used in + /// Indicates a failure where an overflow error in math occurred. Used in /// `_update`. #[derive(Debug)] error ERC20Overflow(); @@ -86,7 +86,7 @@ pub enum Error { /// Indicates a failure with the `spender` to be approved. Used in /// approvals. InvalidSpender(ERC20InvalidSpender), - /// Indicates a failure where an overflow error in math occured. Used in + /// Indicates a failure where an overflow error in math occurred. Used in /// `_update`. Overflow(ERC20Overflow), } @@ -268,6 +268,10 @@ impl ERC20 { /// /// # Errors /// + /// * If the `from` address is `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// * If the `to` address is `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. /// If the `from` address doesn't have enough tokens, then the error /// [`Error::InsufficientBalance`] is returned. fn _transfer( @@ -303,9 +307,12 @@ impl ERC20 { /// * `to` - Recipient's address. /// * `value` - Amount to be transferred. /// - /// # Events + /// # Errors /// - /// * `Transfer` + /// If a math overflow error occurs, then the error + /// [`Error::Overflow`] is returned. + /// If the `from` address doesn't have enough tokens, then the error + /// [`Error::InsufficientBalance`] is returned. pub fn _update( &mut self, from: Address, @@ -356,6 +363,35 @@ impl ERC20 { Ok(()) } + /// Destroys a `value` amount of tokens from `account`, + /// lowering the total supply. + /// + /// Relies on the `update` mechanism. + /// + /// # Arguments + /// + /// * `account` - Owner's address. + /// * `value` - Amount to be burnt. + /// + /// # Errors + /// + /// * If the `from` address is `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// If the `from` address doesn't have enough tokens, then the error + /// [`Error::InsufficientBalance`] is returned. + pub fn _burn( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Error> { + if account == Address::ZERO { + return Err(Error::InvalidSender(ERC20InvalidSender { + sender: Address::ZERO, + })); + } + self._update(account, Address::ZERO, value) + } + /// Updates `owner`'s allowance for `spender` based on spent `value`. /// /// Does not update the allowance value in the case of infinite allowance. diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index afe11a638..54ce7c08b 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1,4 +1,3 @@ * xref:index.adoc[Overview] * xref:crypto.adoc[Cryptography] -