This project demonstrates a modular and upgradeable smart contract architecture using the delegatecall pattern. The Core contract dynamically delegates logic execution to external plugin contracts that share a standard interface.
-
Core.sol – Central contract that manages plugins and delegates calls
-
Plugins (e.g., VaultPlugin.sol) – Independent logic units using deterministic storage slots
-
IPlugin.sol – Common interface for all plugins
├── out/ # Compiled contract ABIs and bytecode
├── broadcast/ # Deployment logs and transactions
│
script/
├── DeployCore.sol # Deployment script for Core
├── DeployPlugins.sol # Deployment script for Plugins
│
src/
├── core/
│ └── Core.sol # The main contract with plugin registry
├── interface/
│ └── IPlugin.sol # Plugin interface
├── plugins/
│ └── ExamplePlugin.sol # Plugin to demonstrate dynamic dispatch
│ └── VaultPlugin.sol # Plugin to manage vaults
│
test/
├── unit/ # Unit Tests
│ └── VaultPlugin.t.sol # Foundry tests for VaultPlugin
│
├── foundry.toml
├── remappings.txt
└── README.md
Note: The
out/folder contains compiled contract ABIs and bytecode. Thebroadcast/folder contains details of recently deployed contracts including calldata and receipts. It’s automatically generated during deployment
- Foundry (install via Foundryup)
forge --versionIf you haven’t already:
curl -L https://foundry.paradigm.xyz | bash
foundryupforge soldeer install[profile.default]
src = "src"
out = "out"
libs = ["lib", "dependencies"]
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }
[dependencies]
forge-std = "1.9.6"
"@openzeppelin-contracts" = "5.3.0"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.3.0/
forge-std/=dependencies/forge-std-1.9.6/src/forge buildSet environment variables:
export SEPOLIA_RPC_URL=https://...
export PRIVATE_KEY=your_private_key
export ETHERSCAN_API_KEY=your_key (optional)IMPORTANT:
Run source .env to load these variables into console.
Deploy with:
forge script script/Deploy.s.sol:Deploy --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY --broadcast --verifyNote: If you don't want to verify the contracts on Etherscan, remove
--verifyfrom the command.
forge testVerbose output (If you want the logs to displayed):
forge test -vvCall registerPlugin(pluginAddress) in Core to register a plugin.
The Core contract delegates to a plugin using:
core.performPluginAction(pluginId, abi.encode(input));This performs a delegatecall into the plugin's performAction method.
To clean build artifacts:
forge cleanAnurag Munda