diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.cargo/config.toml b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.cargo/config.toml new file mode 100644 index 000000000..ead98c8e9 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.cargo/config.toml @@ -0,0 +1,18 @@ +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=-zstack-size=32768", + "-C", "target-feature=-reference-types", + "-C", "target-feature=+bulk-memory", +] + +[target.aarch64-apple-darwin] +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] + +[target.x86_64-apple-darwin] +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.env.example b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.env.example new file mode 100644 index 000000000..63e3e4d09 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.env.example @@ -0,0 +1,3 @@ +RPC_URL= +STYLUS_CONTRACT_ADDRESS= +PRIV_KEY_PATH= diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.github/pull_request_template.md b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.github/pull_request_template.md new file mode 100644 index 000000000..645ad6466 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Description + +Please provide a summary of the changes and any backward incompatibilities. + +## Checklist + +- [ ] I have documented these changes where necessary. +- [ ] I have read the [DCO][DCO] and ensured that these changes comply. +- [ ] I assign this work under its [open source licensing][terms]. + +[DCO]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/DCO.txt +[terms]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/COPYRIGHT.md diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.gitignore b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.gitignore new file mode 100644 index 000000000..fedaa2b1d --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/.gitignore @@ -0,0 +1,2 @@ +/target +.env diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/Cargo.toml b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/Cargo.toml new file mode 100644 index 000000000..2105d85c6 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "stylus-hello-world" +version = "0.1.11" +edition = "2021" +license = "MIT OR Apache-2.0" +homepage = "https://github.com/OffchainLabs/stylus-hello-world" +repository = "https://github.com/OffchainLabs/stylus-hello-world" +keywords = ["arbitrum", "ethereum", "stylus", "alloy"] +description = "Stylus hello world example" + +[dependencies] +alloy-primitives = "=0.8.20" +alloy-sol-types = "=0.8.20" +stylus-sdk = "0.9.0" +hex = { version = "0.4", default-features = false } +motsu = "0.10.0" + +[dev-dependencies] +alloy-primitives = { version = "=0.8.20", features = ["sha3-keccak"] } +tokio = { version = "1.12.0", features = ["full"] } +ethers = "2.0" +eyre = "0.6.8" +stylus-sdk = { version = "0.9.0", features = ["stylus-test"] } +dotenv = "0.15.0" + +[features] +default = ["mini-alloc"] +export-abi = ["stylus-sdk/export-abi"] +debug = ["stylus-sdk/debug"] +mini-alloc = ["stylus-sdk/mini-alloc"] + +[[bin]] +name = "stylus-hello-world" +path = "src/main.rs" + +[lib] +crate-type = ["lib", "cdylib"] + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# If you need to reduce the binary size, it is advisable to try other +# optimization levels, such as "s" and "z" +opt-level = 3 diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/README.md b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/README.md new file mode 100644 index 000000000..158763f8b --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/README.md @@ -0,0 +1,84 @@ +# ERC20 Token Implementation in Stylus + +This project is a complete implementation of the ERC20 token standard written in Rust for Arbitrum Stylus. It demonstrates how to build efficient, gas-optimized smart contracts using Stylus while maintaining full compatibility with the Ethereum ecosystem. + +## What is Stylus? + +Stylus is Arbitrum's next-generation programming environment that allows developers to write smart contracts in Rust, C, and C++ instead of Solidity. These contracts compile to WebAssembly (WASM) and run alongside traditional EVM contracts, offering significantly lower gas costs and improved performance. + +## Contract Structure + +The implementation consists of two main files: + +- `src/lib.rs` - Main contract entry point with token configuration +- `src/erc20.rs` - Core ERC20 implementation with all standard methods + +### Token Configuration +```rust +struct StylusTokenParams; + +impl Erc20Params for StylusTokenParams { + const NAME: &'static str = "StylusToken"; + const SYMBOL: &'static str = "STR"; + const DECIMALS: u8 = 18; +} +``` + +## Quick Start + +### Prerequisites + +Install Rust and the Stylus CLI: + +```bash +# Install Rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install Stylus CLI +cargo install --force cargo-stylus cargo-stylus-check + +# Add WASM target +rustup target add wasm32-unknown-unknown +``` + +### Building the Contract + +```bash +# Check compilation +cargo stylus check + +# Export ABI +cargo stylus export-abi +``` + +### Deployment + +```bash +# Deploy to Stylus testnet +cargo stylus deploy --private-key-path= +``` + +## Gas Efficiency + +Stylus contracts offer significant gas savings compared to Solidity: + +- **Deployment**: ~10x cheaper +- **Execution**: ~5-10x cheaper for compute-heavy operations +- **Storage**: Similar costs to Solidity + + +## Development + +### Testing + +```bash +cargo test +``` + +### Optimization + +For production deployments, optimize the WASM binary: + +```bash +cargo stylus deploy --optimize +``` \ No newline at end of file diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/header.png b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/header.png new file mode 100644 index 000000000..dd12f4675 Binary files /dev/null and b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/header.png differ diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/Apache-2.0 b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/Apache-2.0 new file mode 100644 index 000000000..389377b7a --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/Apache-2.0 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 YOUR COMPANY + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/COPYRIGHT.md b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/COPYRIGHT.md new file mode 100644 index 000000000..7fb8518b2 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/COPYRIGHT.md @@ -0,0 +1,5 @@ +# Licensing Information + +Copyright 2023 YOUR COMPANY + +Except as otherwise noted (below and/or in individual files), this project is licensed under the Apache License, Version 2.0 ([`LICENSE-APACHE`](Apache-2.0) or http://www.apache.org/licenses/LICENSE-2.0) or the MIT license, ([`LICENSE-MIT`](MIT) or http://opensource.org/licenses/MIT), at your option. \ No newline at end of file diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/DCO.txt b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/DCO.txt new file mode 100644 index 000000000..49b8cb054 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/DCO.txt @@ -0,0 +1,34 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/MIT b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/MIT new file mode 100644 index 000000000..686b72558 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/licenses/MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright 2023 YOUR COMPANY + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/rust-toolchain.toml b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/rust-toolchain.toml new file mode 100644 index 000000000..b8889a3bb --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.87.0" diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/erc20.rs b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/erc20.rs new file mode 100644 index 000000000..d2bcb277c --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/erc20.rs @@ -0,0 +1,183 @@ +use alloc::string::String; +use alloc::vec::Vec; +use alloy_primitives::{Address, U256}; +use alloy_sol_types::sol; +use core::marker::PhantomData; +use stylus_sdk::{ + evm, + msg::{self, sender}, + prelude::*, +}; + +pub trait Erc20Params { + const NAME: &'static str; + + const SYMBOL: &'static str; + + const DECIMALS: u8; +} + +sol_storage! { + /// Erc20 implements all ERC-20 methods. + pub struct Erc20 { + /// Maps users to balances + mapping(address => uint256) balances; + /// Maps users to a mapping of each spender's allowance + mapping(address => mapping(address => uint256)) allowances; + /// The total supply of the token + uint256 total_supply; + /// Used to allow [`Erc20Params`] + PhantomData phantom; + } +} + +sol! { + event Transfer(address indexed from , address indexed to , uint256 value); + event Approval(address indexed owner,address indexed spender,uint256 value); + + error InsufficientBalance(address from,uint256 have,uint256 want); + error InsufficientBalances(address from,uint256 have,uint256 want); + + + error InsufficientAllowance(address owner,address spender ,uint256 have , uint256 want); +} + +#[derive(SolidityError)] +pub enum Erc20Error { + InsufficientBalance(InsufficientBalance), + InsufficientAllowance(InsufficientAllowance), +} + +impl Erc20 { + pub fn _transfer(&mut self, from: Address, to: Address, value: U256) -> Result<(), Erc20Error> { + let mut sender_balance = self.balances.setter(from); + + let old_sender_balance = sender_balance.get(); + + if old_sender_balance < value { + return Err(Erc20Error::InsufficientBalance(InsufficientBalance { + from, + have: old_sender_balance, + want: value, + })); + } + sender_balance.set(old_sender_balance - value); + + let mut to_balance = self.balances.setter(to); + let new_to_balance = to_balance.get() + value; + to_balance.set(new_to_balance); + + evm::log(Transfer { from, to, value }); + Ok(()) + } + + pub fn mint(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> { + let mut balance = self.balances.setter(address); + let new_balance = balance.get() + value; + + balance.set(new_balance); + + self.total_supply.set(self.total_supply.get() + value); + + // Emitting the transfer event + evm::log(Transfer { + from: Address::ZERO, + to: address, + value, + }); + Ok(()) + } + + pub fn burn(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> { + // Decreasing balance + let mut balance = self.balances.setter(address); + let old_balance = balance.get(); + if old_balance < value { + return Err(Erc20Error::InsufficientBalance(InsufficientBalance { + from: address, + have: old_balance, + want: value, + })); + } + balance.set(old_balance - value); + + self.total_supply.set(self.total_supply.get() - value); + + //Emitting the transfer event + + evm::log(Transfer { + from: address, + to: Address::ZERO, + value, + }); + Ok(()) + } +} + +#[public] +impl Erc20 { + pub fn name() -> String { + T::NAME.into() + } + + ///Immutable token symbol + pub fn symbol() -> String { + T::SYMBOL.into() + } + + ///Total supply of tokens + pub fn total_supply(&self) -> U256 { + self.total_supply.get() + } + + ///Balance of `address` + pub fn balance_of(&self, owner: Address) -> U256 { + self.balances.get(owner) + } + + pub fn transfer(&mut self, to: Address, value: U256) -> Result { + self._transfer(msg::sender(), to, value)?; + Ok(true) + } + + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + ///check msg::sender() allowance + let mut sender_allowances = self.allowances.setter(from); + let mut allowance = sender_allowances.setter(msg::sender()); + + let old_allowance = allowance.get(); + if old_allowance < value { + return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance { + owner: from, + spender: msg::sender(), + have: old_allowance, + want: value, + })); + } + + allowance.set(old_allowance - value); + + self._transfer(from, to, value); + + Ok(true) + } + + pub fn approve(&mut self, spender: Address, value: U256) -> bool { + self.allowances.setter(msg::sender()).insert(spender, value); + evm::log(Approval { + owner: msg::sender(), + spender, + value, + }); + true + } + + pub fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.allowances.getter(owner).get(spender) + } +} diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/interface.rs b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/interface.rs new file mode 100644 index 000000000..7ddeb39a2 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/interface.rs @@ -0,0 +1,29 @@ + +mod token{ + #![allow(missing_docs)] + #![cfg_attr(coverage_nightly,coverage(off))] + use alloc::vec; + + use stylus_sdk::prelude::sol_interface; + + sol_interface!{ + interface Erc20Interface{ + function totalSupply() external view returns(uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + } + } + + sol_interface! { + interface IErc20MetadataInterface{ + function name() external view returns(string); + + function symbol() external view returns(string); + + function decimals() external view returns(uint8); + } + } +} \ No newline at end of file diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/lib.rs b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/lib.rs new file mode 100644 index 000000000..0cec343f2 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/lib.rs @@ -0,0 +1,51 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] +#![cfg_attr(not(any(test, feature = "export-abi")), no_std)] + +#[macro_use] +extern crate alloc; + +use alloc::vec::Vec; + +mod erc20; +mod interface; +mod r#mod; + + +/// Import items from the SDK. The prelude contains common traits and macros. +use stylus_sdk::{alloy_primitives::U256, msg, prelude::*}; + +use crate::erc20::{Erc20, Erc20Error, Erc20Params}; + +struct StylusTokenParams; + +impl Erc20Params for StylusTokenParams { + const NAME: &'static str = "StylusToken"; + const SYMBOL: &'static str = "STR"; + const DECIMALS: u8 = 18; +} + + +// Define some persistent storage using the Solidity ABI. +// `Counter` will be the entrypoint. +sol_storage! { + #[entrypoint] + struct StylusToken { + #[borrow] + Erc20 erc20 + } +} + +#[public] +#[inherit(Erc20)] +impl StylusToken { + //mint tokens + pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> { + self.erc20.mint(msg::sender(), value)?; + Ok(()) + } + + pub fn mint_to(&mut self, value: U256) -> Result<(), Erc20Error> { + self.erc20.burn(msg::sender(), value); + Ok(()) + } +} diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/main.rs b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/main.rs new file mode 100644 index 000000000..f0f199245 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] + +#[cfg(not(any(test, feature = "export-abi")))] +#[no_mangle] +pub extern "C" fn main() {} + +#[cfg(feature = "export-abi")] +fn main() { + stylus_hello_world::print_from_args(); +} diff --git a/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/mod.rs b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/mod.rs new file mode 100644 index 000000000..db95cb555 --- /dev/null +++ b/submissions/week-4-codes/day-5/Ofuzor-Chukwuemeke/erc20/src/mod.rs @@ -0,0 +1,265 @@ +use alloc::{vec, vec::Vec}; +use alloy_primitives::aliases::B32; +// use alloy_primitives::{aliases::B32,Address,U256}; +// use stylus_sdk::{call::MethodCall,evm,log,prelude::*,storage::{StorageMap,StorageU256}}; +pub use sol::*; +use stylus_sdk::{ + alloy_primitives::{Address, U256}, + call::{Call, Error, MethodError}, + evm, msg, + prelude::*, + storage::{StorageMap, StorageU256}, +}; + +mod sol { + use alloy_sol_types::sol; + + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + event Transfer(address indexed from , address indexed to , uint256 value); + + #[derive(Debug)] + #[allow(missing_docs)] + event Approval(address indexed owner,address indexed spender,uint256 value); + } + + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + error ERCInsufficientBalance(address sender, uint256 balance,uint256 needed); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidSender(address sender); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidReceiver(address receiver); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InsufficientApprover(address approver); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidApprover(address approver); + } +} + +#[derive(SolidityError, Debug)] +pub enum Erc20Error { + InsufficientBalance(ERCInsufficientBalance), + InvalidSender(ERC20InvalidSender), + InvalidReceiver(ERC20InvalidReceiver), + InsufficientAllowance(ERC20InsufficientAllowance), + InsufficientApprover(ERC20InsufficientApprover), + InvalidApprover(ERC20InvalidApprover), +} + +impl MethodError for Erc20Error { + fn encode(self) -> alloc::vec::Vec { + self.into() + } +} + +#[storage] +pub struct Erc20 { + pub(crate) balances: StorageMap, + pub(crate) allowances: StorageMap>, + pub(crate) total_supply: StorageU256, +} + +unsafe impl TopLevelStorage for Erc20 {} + +#[interface_id] +pub trait IErc20 { + type Error: Into>; + + fn total_supply(&self) -> U256; + + fn balance_of(&self, account: Address) -> U256; + + fn transfer(&mut self, to: Address, value: U256) -> Result; + + fn allowance(&self, owner: Address, spender: Address) -> U256; + + fn approve(&mut self, spender: Address, value: U256) -> Result; + + fn transfer_from(&mut self, from: Address, to: Address, value: U256); +} + +#[public] +#[implements(IErc20)] +impl Erc20 {} + +#[public] +impl IErc20 for Erc20 { + type Error = Erc20Error; + + fn total_supply(&self) -> U256 { + self.total_supply.get() + } + + fn balance_of(&self, account: Address) -> U256 { + self.balances.get(account) + } + + fn transfer(&mut self, to: Address, value: U256) -> Result { + let from = msg::sender(); + self._transfer(from, to, value)?; + Ok(true) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.allowances.get(owner).get(spender) + } + + fn approve(&mut self, spender: Address, value: U256) -> Result { + let owner = msg::sender(); + self._approve(owner, spender, value, true); + } + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + let spender = msg::sender(); + self._spend_allowance(from, spender, value)?; + self._transfer(from, to, value)?; + Ok(true) + } +} + +impl Erc20 { + fn _approve( + &mut self, + owner: Address, + spender: Address, + value: U256, + emit_event: bool, + ) -> Result { + if owner.is_zero() { + return Err(Erc20Error::InvalidApprover(ERC20InvalidApprover { + approver: Address::ZERO, + })); + } + if spender.is_zero() { + return Err(Erc20Error::InvalidSender(ERC20InvalidSender { + sender: Address::ZERO, + })); + } + self.allowances.setter(owner).insert(spender, value); + if emit_event { + evm::log(Approval { + owner, + spender, + value, + }); + }; + } + + fn _transfer(&mut self, from: Address, to: Address, value: U256) -> Result<(), Erc20Error> { + if from.is_zero() { + return Err(Erc20Error::InvalidSender(ERC20InvalidSender { + sender: Address::ZERO, + })); + } + if to.is_zero() { + return Err(Erc20Error::InvalidReceiver(ERC20InvalidReceiver { + receiver: Address::ZERO, + })); + } + + self._update(from, to, value)?; + + Ok(()) + } + + pub fn _mint(&mut self, account: Address, value: U256) -> Result<(), Erc20Error> { + if account.is_zero() { + return Err(Erc20Error::InvalidReceiver(ERC20InvalidReceiver { + receiver: Address::ZERO, + })); + } + self._update(Address::ZERO, account, value) + } + pub fn _update(&mut self, from: Address, to: Address, value: U256) -> Result<(), Erc20Error> { + if from.is_zero() { + // Mint operation. Overflow check required: the rest of the code + // assumes that `total_supply` never overflows. + self.total_supply + .add_assign_checked(value, "should not exceed `U256::MAX` for `total_supply`"); + } else { + let from_balance = self.balances.get(from); + if from_balance < value { + return Err(Erc20Error::InsufficientBalance(ERCInsufficientBalance { + sender: from, + balance: from_balance, + needed: value, + })); + } + // Overflow not possible: + // `value` <= `from_balance` <= `total_supply`. + self.balances.setter(from).set(from_balance - value); + } + + if to.is_zero() { + // Overflow not possible: + // `value` <= `total_supply` or + // `value` <= `from_balance` <= `total_supply`. + self.total_supply.sub_assign_unchecked(value); + } else { + // Overflow not possible: + // `balance_to` + `value` is at most `total_supply`, + // which fits into a `U256`. + self.balances.setter(to).add_assign_unchecked(value); + } + + evm::log(Transfer { from, to, value }); + + Ok(()) + } + + pub fn _burn(&mut self, account: Address, value: U256) -> Result<(), Erc20Error> { + if account == Address::ZERO { + return Err(Erc20Error::InvalidSender(ERC20InvalidSender { + sender: Address::ZERO, + })); + } + self._update(account, Address::ZERO, value) + } +} + +impl IErc165 for Erc20 { + fn supports_interface(&self, interface_id: B32) -> bool { + ::interface_id() == interface_id + || ::interface_id() == interface_id + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::{uint, Address, U256}; + use alloy_sol_types::sol_data::Uint; + use motsu::prelude::*; + + use super::*; + + #[motsu::test] + fn mint(contract: Contract, alice: Address) { + let one = Uint::from(1); + let initial_balance = contract.sender(alice).balance_of(alice); + + let result = contract.sender(alice)._mint(alice, one); + + assert!(result.is_ok()); + + assert_eq!(initial_balance + one, contract.sender(alice).total_supply()) + } +}