How to Deploy with Proxies
Proxies enable contract upgrades by separating logic and storage. This guide covers deploying upgradeable contracts using hardhat-deploy's proxy features.
Prerequisites
Install the proxy extension:
bash
npm install -D @rocketh/proxybash
pnpm add -D @rocketh/proxyAdd it to your rocketh/config.ts:
typescript
import * as proxyExtensions from '@rocketh/proxy';
import * as deployExtensions from '@rocketh/deploy';
const extensions = {...deployExtensions, ...proxyExtensions};
export {extensions};Proxy Types
hardhat-deploy supports several proxy patterns:
1. Transparent Proxy (OpenZeppelin)
- Admin can upgrade, users can call functions
- Gas overhead on every call
- Most secure for production
2. UUPS (Universal Upgradeable Proxy Standard)
- Upgrade logic in implementation contract
- Lower gas costs
- Implementation must include upgrade functionality
3. Beacon Proxy
- Multiple proxies share same implementation
- Efficient for deploying many identical contracts
Basic Proxy Deployment
Transparent Proxy
typescript
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(
async ({ deployViaProxy, namedAccounts }) => {
const { deployer, admin } = namedAccounts;
await deployViaProxy(
"MyContract", // Contract name
{
account: deployer,
artifact: artifacts.Greeter,
args: ["initialValue"], // Constructor arguments
},
{
owner: admin, // Proxy admin
proxyContract: "SharedAdminOptimizedTransparentProxy", // Proxy type
}
);
},
{ tags: ["MyContract"] }
);UUPS Proxy
typescript
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(
async ({ deployViaProxy, namedAccounts }) => {
const { deployer, admin } = namedAccounts;
await deployViaProxy(
"MyUUPSContract",
{
account: deployer,
artifact: artifacts.MyUUPSContract,
args: [admin, "initialValue"],
},
{
owner: admin,
proxyContract: "UUPS", // UUPS proxy type
execute: "initialize", // Initialization function
}
);
},
{ tags: ["MyUUPSContract"] }
);Contract Upgrades
Upgrading Implementation
When you modify your contract and redeploy, hardhat-deploy automatically detects changes and upgrades the implementation:
typescript
// After modifying your contract source code:
// 1. Recompile: npx hardhat compile
// 2. Redeploy: npx hardhat deploy
// hardhat-deploy will:
// - Detect that the implementation bytecode has changed
// - Deploy a new implementation contract
// - Call upgradeToAndCall (or upgrade) on the proxy
// - Keep the same proxy address and storageChecking for Upgrades
You can also check if an upgrade is needed before deploying:
typescript
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(
async ({ deployViaProxy, namedAccounts }) => {
const { deployer, admin } = namedAccounts;
const result = await deployViaProxy(
"MyContract",
{
account: deployer,
artifact: artifacts.MyContract,
args: ["initialValue"],
},
{
owner: admin,
proxyContract: "SharedAdminOptimizedTransparentProxy",
}
);
if (result.newlyDeployed) {
console.log("Contract was upgraded or newly deployed");
} else {
console.log("Contract unchanged, no upgrade needed");
}
},
{ tags: ["MyContract"] }
);Advanced Proxy Patterns
Initialization Function
For contracts that need initialization after deployment:
typescript
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(
async ({ deployViaProxy, namedAccounts }) => {
const { deployer, admin } = namedAccounts;
await deployViaProxy(
"MyContract",
{
account: deployer,
artifact: artifacts.Greeter,
args: [""], // No constructor args
},
{
owner: admin,
proxyContract: "SharedAdminOptimizedTransparentProxy",
execute: {
methodName: "initialize",
args: [admin, deployer, 1000], // Initialization parameters
},
}
);
},
{ tags: ["MyContract"] }
);Next Steps
- Deploy Diamond Contracts for modular upgrades
- Deployment Fixtures in Tests for testing proxy deployments