Welcome to the Blok-a-Thon, a Blok Capital hackathon focused on building modular smart contract facets using the Diamond Proxy pattern (EIP-2535). This repository provides a ready-to-use Foundry setup with a fully configured Diamond Proxy architecture.
This is a Facet-Building Hackathon where participants create modular smart contract functionality (facets) that plug into a Diamond Proxy. Instead of building contracts from scratch, you'll leverage the power of the Diamond standard to create composable, upgradeable features.
Build DeFi tools that help users manage and grow their assets for the long term. Think wealth building, not speculation.
Examples:
- Token swap mechanisms (like Uniswap)
- Lending and borrowing protocols (like Aave)
- Yield farming strategies
- Any DeFi logic focused on wealth preservation and growth
- Arbitrum One (ARB)
- Polygon (POL)
- Avalanche (AVAX)
- Base
- BNB Smart Chain (BNB)
The Diamond Proxy pattern allows a single contract to use multiple implementation contracts (facets) through delegatecall. This enables:
- Modularity: Add, replace, or remove functionality without redeploying everything
- Unlimited Contract Size: Bypass the 24KB contract size limit
- Shared State: All facets share the same storage
- Upgradeability: Upgrade parts of your system independently
- Diamond: The main proxy contract that delegates calls to facets
- Facets: Implementation contracts containing specific functionality
- Function Selectors: 4-byte identifiers mapping functions to their respective facets
- DiamondCut: The mechanism for adding/replacing/removing facets
Resources:
- Foundry installed
- Basic understanding of Solidity
- Git installed
# Fork this repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/Blokathon-Foundry.git
cd Blokathon-Foundry
# Install dependencies
forge install# Copy the example environment file
cp .envExample .env
# Edit .env with your credentials
nano .env # or use your preferred editor.env file structure:
PRIVATE_KEY_ANVIL=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
RPC_URL_ANVIL=http://127.0.0.1:8545
# For deploying to real networks
PRIVATE_KEY=your_private_key_here
RPC_URL_ARBITRUM=https://arb1.arbitrum.io/rpc
RPC_URL_POLYGON=https://polygon-rpc.com
RPC_URL_AVALANCHE=https://api.avax.network/ext/bc/C/rpc
RPC_URL_BASE=https://mainnet.base.org
RPC_URL_BSC=https://bsc-dataseed.binance.org
# Etherscan API keys for verification
API_KEY_ETHERSCAN=your_etherscan_api_key
API_KEY_ARBISCAN=your_arbiscan_api_key
API_KEY_POLYGONSCAN=your_polygonscan_api_key
API_KEY_SNOWTRACE=your_snowtrace_api_key
API_KEY_BASESCAN=your_basescan_api_key
API_KEY_BSCSCAN=your_bscscan_api_keysource .envforge buildforge test
# Run with verbosity
forge test -vvv
# Run specific test
forge test --match-test testFunctionNameforge fmtforge snapshotforge cleanTerminal 1 - Start Anvil:
anvilTerminal 2 - Deploy Diamond:
source .env
forge script script/Deploy.s.sol \
--rpc-url $RPC_URL_ANVIL \
--private-key $PRIVATE_KEY_ANVIL \
--broadcastImportant: If you use a different private key variable name in your .env, update the corresponding line in script/Deploy.s.sol:
bytes32 privateKey = vm.envBytes32("YOUR_PRIVATE_KEY_NAME");source .env
forge script script/Deploy.s.sol \
--rpc-url $RPC_URL_ARBITRUM \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--slow \
--etherscan-api-key $API_KEY_ARBISCANReplace $RPC_URL_ARBITRUM and $API_KEY_ARBISCAN with the appropriate variables for your target chain:
- Polygon:
$RPC_URL_POLYGON,$API_KEY_POLYGONSCAN - Avalanche:
$RPC_URL_AVALANCHE,$API_KEY_SNOWTRACE - Base:
$RPC_URL_BASE,$API_KEY_BASESCAN - BSC:
$RPC_URL_BSC,$API_KEY_BSCSCAN
If deployment succeeds but Etherscan verification fails:
forge script script/Deploy.s.sol \
--rpc-url $RPC_URL_ARBITRUM \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--slow \
--resume \
--etherscan-api-key $API_KEY_ARBISCANAfter the Diamond is deployed, you can add new facets:
forge script script/DeployFacet.s.sol \
--rpc-url $RPC_URL_ANVIL \
--private-key $PRIVATE_KEY_ANVIL \
--broadcastBlokathon-Foundry/
βββ src/
β βββ Diamond.sol # Main Diamond proxy contract
β βββ facets/
β β βββ Facet.sol # Base facet contract
β β βββ baseFacets/ # Core Diamond facets
β β β βββ cut/ # DiamondCut functionality
β β β βββ loupe/ # DiamondLoupe for introspection
β β β βββ ownership/ # Ownership management
β β βββ utilityFacets/ # Your custom facets go here!
β βββ interfaces/ # Interface definitions
β βββ libraries/ # Shared libraries
βββ script/
β βββ Deploy.s.sol # Diamond deployment script
β βββ DeployFacet.s.sol # Facet deployment script
β βββ Base.s.sol # Base script utilities
βββ test/ # Test files
βββ .envExample # Example environment variables
βββ README.md # This file
Create four files in src/facets/utilityFacets/:
YourFacetStorage.sol- Storage structIYourFacet.sol- InterfaceYourFacetBase.sol- Internal logicYourFacet.sol- Public-facing facet
YourFacetStorage.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library YourFacetStorage {
bytes32 constant STORAGE_POSITION = keccak256("your.facet.storage");
struct Layout {
mapping(address => uint256) balances;
uint256 totalSupply;
}
function layout() internal pure returns (Layout storage l) {
bytes32 position = STORAGE_POSITION;
assembly {
l.slot := position
}
}
}IYourFacet.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYourFacet {
function yourFunction() external returns (uint256);
}YourFacetBase.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./YourFacetStorage.sol";
contract YourFacetBase {
function _yourInternalLogic() internal view returns (uint256) {
YourFacetStorage.Layout storage l = YourFacetStorage.layout();
return l.totalSupply;
}
}YourFacet.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../Facet.sol";
import "./YourFacetBase.sol";
import "./IYourFacet.sol";
contract YourFacet is Facet, YourFacetBase, IYourFacet {
function yourFunction() external override returns (uint256) {
return _yourInternalLogic();
}
}Create a test file in test/:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Diamond.sol";
import "../src/facets/utilityFacets/YourFacet.sol";
contract YourFacetTest is Test {
Diamond diamond;
YourFacet yourFacet;
function setUp() public {
// Deploy and configure diamond
diamond = new Diamond(address(this));
yourFacet = new YourFacet();
// Add facet to diamond using DiamondCut
// ... (cut logic here)
}
function testYourFunction() public {
// Your test logic
}
}Update script/DeployFacet.s.sol with your facet's deployment logic, then run:
forge script script/DeployFacet.s.sol \
--rpc-url $RPC_URL_ANVIL \
--private-key $PRIVATE_KEY_ANVIL \
--broadcast# Get all facets
cast call $DIAMOND_ADDRESS "facets()" --rpc-url $RPC_URL_ANVIL
# Get facet address for a function
cast call $DIAMOND_ADDRESS "facetAddress(bytes4)" $FUNCTION_SELECTOR --rpc-url $RPC_URL_ANVIL
# Call your custom function
cast call $DIAMOND_ADDRESS "yourFunction()" --rpc-url $RPC_URL_ANVILcast send $DIAMOND_ADDRESS "yourFunction(uint256)" 100 \
--private-key $PRIVATE_KEY_ANVIL \
--rpc-url $RPC_URL_ANVIL- Foundry Book: https://book.getfoundry.sh/
- EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535
- Diamond Pattern Guide: https://eip2535diamonds.substack.com/
- Solidity Documentation: https://docs.soliditylang.org/
- Start Simple: Begin with a basic facet and iterate
- Read EIP-2535: Understanding the Diamond pattern is crucial
- Use Storage Properly: Each facet should use namespaced storage to avoid collisions
- Test Thoroughly: Write comprehensive tests for your facet
- Focus on Wealth Management: Build tools that help users grow and preserve assets
- Consider Security: Use OpenZeppelin libraries when possible
- Document Your Code: Clear comments help judges understand your work
- Review existing facets in
src/facets/for examples - Check the Foundry documentation for tooling questions
- Study the Diamond proxy implementation in
src/Diamond.sol