Author: Adam Vožda (xvozdaa00)
- Table of contents
- Project summary
- Setup instructions
- Technology Stack
- Project Structure
- Smart Contracts
- Frontend (dApp)
- Implementation Details
- Slither Analysis
- Testing Approach
- License
This project implements an modified ERC20 token with max supply, identity verification, transfer restrictions, and multi-role administration systems. It consists of a smart contract backend written in Solidity and a frontend application built with React and Wagmi.
Before getting started, ensure you have the following installed:
- Node.js (v18 or later)
- Foundry - Ethereum development framework
- Git - Version control system
- Start local blockchain network:
make network- In a new terminal, deploy the smart contract:
make deploy- Start the frontend development server:
make dapp- Run tests (optional):
make test- Navigate to the smart-contracts directory:
cd smart-contracts- Install dependencies:
forge install- Run a local blockchain:
anvil --mnemonic "test test test test test test test test test test test junk"It is recommended to use the same mnemonic for all deployments so that the prefilled addresses are always the same. Admin addresses can be modified in ./script/DeployBDAERC20.s.sol file.
- Deploy the contract:
forge script script/DeployBDAERC20.s.sol --rpc-url http://localhost:8545 --private-key <private-key> --broadcast- Navigate to the frontend directory:
cd frontend- Install dependencies:
npm install- Create a
.envfile with the contract address:
VITE_CONTRACT_ADDRESS=0x... # The deployed contract address- Start the development server:
npm run dev- Solidity: v0.8.29 for smart contract implementation
- Foundry: Development framework for compilation, testing, and deployment
- OpenZeppelin Contracts: Used for base ERC20 implementation and cryptography utilities
- React: Frontend library for building user interfaces
- Vite: Build tool and development server
- Wagmi: React hooks for Ethereum
- Tailwind CSS: Utility-first CSS framework for styling
- Shadcn/UI: Component library for building the interface
modified-ERC20-dApp/
├── frontend/ # React frontend application
│ ├── src/ # Source code
│ │ ├── components/ # UI components
│ │ ├── lib/ # Utility functions and context providers
│ │ ├── pages/ # Application pages
│ │ └── types/ # TypeScript type definitions
├── smart-contracts/ # Solidity smart contracts
│ ├── src/ # Contract source code
│ │ ├── AdminRoles.sol # Admin role management
│ │ ├── BDAERC20.sol # Main token contract
│ │ ├── IdentityVerification.sol # Identity verification
│ │ └── TransferLimitation.sol # Transfer restrictions
│ ├── script/ # Deployment scripts
│ └── test/ # Contract tests
The project consists of multiple smart contracts with specific responsibilities:
The main token contract that extends OpenZeppelin's ERC20 implementation with additional features:
- Minting with daily limits and max supply limit
- Transfer restrictions based on user verification and limits
- Allowance approval
Manages multiple types of administrative roles with democratic voting mechanisms:
- Minting admins: Control token minting
- Restriction admins: Manage transfer limits
- Identity Provider admins: Manage identity verification providers, manual verification/unverification/blockation/unblokcation of addresses
Handles user identity verification:
- Maintains a registry of trusted identity providers
- Allows users to verify their identity using cryptographic signatures
- Manages address blocking/unblocking
- Implements expiration for verifications
Implements transfer restrictions:
- Daily transfer limits per address
- Daily mint limits per address
- Automatic daily limit reset
The frontend application provides an interface to interact with the smart contract:
- Auth: Page for metamask wallet connection

- Dashboard: Main user interface showing token balance, transfer limit status, verification status and transfer forms

- Mint: Token minting interface, only for mintAdmin

- Approval: Token approval management, only for verified addresses

- Address Management: Interface for blocking/unblocking addresses and manual verification, only for idpAdmins

- Transfer Limits: Interface for setting transfer limits, only for restrAdmins

- Identity Providers: Interface for adding/removing identity providers, only for idpAdmins

- Admin Voting: Voting interface for each admin role

Each address starts as unverified and can be verified by an identity provider. The verification process involves generating a signature using the identity provider private key and submitting it to the contract. The contract then verifies the signature using the public key of the identity provider. The contract saves timestap of verification, if this timestamp + expirationTime (passed in constructor) is less than current timestamp, the address is considered as unverified. Verified address can be reverified before expiration to extend the expiration period. Address can be manually verified/unverified by idp admins. Address can be blocked by idpAdmin which causes the address to behave as unverified and has to be unblocked by idp admin before being able to reach the verified status again.
For the purposes of this project identity provider is mocked in ./frontend/src/lib/mock-identity-provider
Each admin role has a simple voting mechanism. Admins can vote for addresses, if this address is not an admin yet the vote is considered as proposal to add this address to admin role, if this address is already an admin this vote is considered as proposal to remove this address from admin role. If more than half of admins vote for given address, the voting is considered as approved. Each admin can vote only once per address.
This is a very simple system that has some restrictions:
- The interface is unable to show details about ongoing voting processes
- Votes can not be changed after they are cast
The contract implements automatic daily limit resets using a transaction-based approach in the checkLimitRefresh() modifier:
modifier checkLimitRefresh() {
if ((block.timestamp / 1 days) > (lastRefreshTimestamp / 1 days)) {
lastRefreshTimestamp = block.timestamp;
for (uint256 i = 0; i < balanceHolders.length; i++) {
address user = balanceHolders[i];
dailyTransferredAmount[user] = 0;
dailyMintedAmount[user] = 0;
}
}
_;
}This modifier is called on every transaction that involves token transfer or minting.
Advantages of this approach:
- Automatic execution: No need for manual intervention or external services to trigger the reset
- Decentralized operation: Fully on-chain without dependencies on external schedulers
- Storage efficiency: Uses existing
balanceHoldersarray instead of duplicate structures
Disadvantages of this approach:
- Gas costs scale with user count: Reset operation iterates through all balance holders
- Cost burden on first user: The first user to transact after midnight bears the gas cost of resetting all limits
- Outdated state: The interface could show outdated information until the next reset
Alternative approaches considered:
- Admin-triggered reset function: More controlled but introduces centralization
- Individual timestamp tracking per user: Better gas distribution but more complex and storage-intensive
- Oracle-based reset: Predictable timing but requires external dependencies
For purposes of this project the current approach offers a good balance between decentralization and efficiency. For larger deployments some optimalizations should be considered.
Security analysis has been performed using Slither, a popular static analysis tool for Solidity. Here's a summary of the key findings:
No critical or high severity vulnerabilities were detected.
-
Dangerous Strict Equalities - In
IdentityVerification.isVerified(), the code uses a dangerous strict equality checkverifiedAddresses[user] == 0. This could potentially be problematic if an unconventional value is used to represent unverified addresses. -
Timestamp Dependency - Several functions rely on
block.timestampfor comparisons, including verification expiration checks and daily limit resets:BDAERC20.getAddressInfo()IdentityVerification.isVerified()TransferLimitation.addBalanceHolder()
While necessary for our use case, this introduces a minor vulnerability as miners can manipulate timestamps within a small window.
-
Variable Shadowing -
BDAERC20.getAddressInfo(address).isVerifiedshadows the functionIdentityVerification.isVerified(address), which could lead to confusion during development and maintenance.
-
Different Solidity Versions - Our codebase uses two different versions of Solidity:
- Version
^0.8.20in OpenZeppelin dependencies - Version
0.8.29in our custom contracts
This inconsistency could potentially lead to unexpected behaviors.
- Version
-
Known Issues in Solidity Compiler - The version constraint
^0.8.20contains known issues including:- VerbatimInvalidDeduplication
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
-
Uncached Array Length - In
TransferLimitation.sol, the loop conditioni < balanceHolders.lengthshould use a cached array length instead of repeatedly accessing the storage array's length property, which consumes more gas. -
Assembly Usage - The OpenZeppelin
ECDSA.tryRecover()function uses inline assembly. While not a security issue, it's noted as it may complicate code audits.
The testing approach combines unit testing with Foundry's simulation capabilities to ensure correct smart contract functionality.
EVM functions uset for testing:
-
Time Manipulation:
vm.warp()simulates time passage for:- Daily limit resets
- Identity verification expirations
-
Address Impersonation:
vm.prank()enables:- Verifying access control
- Simulating multi-user interactions
Each test file follows a structured approach:
ContractNameTest.t.sol
├── setUp() - Initializes contracts with test configurations
├── testCoreFunctionality() - Primary contract operations
├── testEdgeCases() - Boundary conditions and limits
├── testRevertConditions() - Expected failure scenarios
├── testAdminFunctions() - Role-based access control
└── testModifiers() - Modifier behavior validation
-
Admin Role Management:
- Voting systems for adding/removing admins
- Majority requirement validation
- Duplicate vote prevention
-
Token Operations:
- Minting with daily limits
- Daily transfer limits
- Verified user transfers/approvals
-
Identity Verification:
- Address verification/blocking
- Provider management
- Expiration handling
This project is licensed under the APGL-3.0 License. See the LICENSE file for details.
