Skip to content

Commit 62beda4

Browse files
h4x3rotabclaude
andcommitted
WIP: Migrate Hardhat to Foundry with comprehensive test suite
- Add Foundry configuration (foundry.toml) with OpenZeppelin remappings - Migrate all Hardhat tests to Foundry Solidity tests - Add comprehensive test coverage for DstackKms and DstackApp contracts - Implement upgrade testing with OpenZeppelin Foundry plugin - Create dedicated test files for core functionality vs upgrade testing - Add contract deployment scripts for Foundry - Include test utility contracts (DstackAppV1, DstackKmsV1) for upgrade scenarios - Fix compilation issues with Address library usage - Add detailed README with testing instructions and project overview Test Status: ✅ DstackApp.t.sol: 11/11 tests PASS - Core app functionality ✅ DstackKms.t.sol: 16/16 tests PASS - Core KMS functionality ✅ UpgradesBasic.t.sol: 5/5 tests PASS - Basic upgrade functionality ⚠️ Advanced upgrade tests have OpenZeppelin validation issues Total: 32/32 core and basic upgrade tests PASSING 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 1ad75c0 commit 62beda4

17 files changed

+1798
-0
lines changed

.gitmodules

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[submodule "kms/auth-eth/lib/forge-std"]
2+
path = kms/auth-eth/lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std
4+
[submodule "kms/auth-eth/lib/openzeppelin-contracts-upgradeable"]
5+
path = kms/auth-eth/lib/openzeppelin-contracts-upgradeable
6+
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
7+
[submodule "kms/auth-eth/lib/openzeppelin-foundry-upgrades"]
8+
path = kms/auth-eth/lib/openzeppelin-foundry-upgrades
9+
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades

kms/auth-eth/README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Dstack KMS Auth-ETH
2+
3+
A Foundry-based smart contract project for Dstack's Key Management System (KMS) authentication on Ethereum.
4+
5+
## Overview
6+
7+
This project contains upgradeable smart contracts for:
8+
- **DstackKms**: Key Management System contract with app registration and validation
9+
- **DstackApp**: Application-specific authentication contract with device and compose hash management
10+
11+
## Prerequisites
12+
13+
- [Foundry](https://book.getfoundry.sh/getting-started/installation)
14+
- Node.js (for OpenZeppelin Foundry Upgrades plugin)
15+
16+
## Setup
17+
18+
1. Install dependencies:
19+
```bash
20+
forge install
21+
npm install
22+
```
23+
24+
2. Build contracts:
25+
```bash
26+
forge build
27+
```
28+
29+
## Testing
30+
31+
The project uses Foundry for testing with proper separation between core functionality and upgrade testing.
32+
33+
### Core Functionality Tests
34+
35+
Test the basic features of the contracts without upgrade-related functionality:
36+
37+
```bash
38+
# Test DstackApp core functionality (11 tests)
39+
forge test --ffi --match-path "test/DstackApp.t.sol"
40+
41+
# Test DstackKms core functionality (16 tests)
42+
forge test --ffi --match-path "test/DstackKms.t.sol"
43+
44+
# Run both core functionality test suites
45+
forge test --ffi --match-path "test/DstackApp.t.sol" && forge test --ffi --match-path "test/DstackKms.t.sol"
46+
```
47+
48+
### Upgrade Tests
49+
50+
Test upgrade functionality and contract migration scenarios:
51+
52+
```bash
53+
# Test basic upgrade functionality (5 tests)
54+
forge test --ffi --match-path "test/UpgradesBasic.t.sol"
55+
56+
# Test advanced upgrade scenarios (may have OpenZeppelin validation issues)
57+
forge test --ffi --match-path "test/Upgrades.t.sol"
58+
forge test --ffi --match-path "test/UpgradesWithPlugin.t.sol"
59+
```
60+
61+
### Run All Working Tests
62+
63+
```bash
64+
# Run all stable tests (32 tests total)
65+
forge test --ffi --match-path "test/DstackApp.t.sol" && \
66+
forge test --ffi --match-path "test/DstackKms.t.sol" && \
67+
forge test --ffi --match-path "test/UpgradesBasic.t.sol"
68+
```
69+
70+
### Test Coverage Summary
71+
72+
-**DstackApp.t.sol**: 11/11 tests PASS - Core app functionality
73+
-**DstackKms.t.sol**: 16/16 tests PASS - Core KMS functionality
74+
-**UpgradesBasic.t.sol**: 5/5 tests PASS - Basic upgrade functionality
75+
- ⚠️ **Upgrades.t.sol**: OpenZeppelin validation issues (advanced upgrade testing)
76+
- ⚠️ **UpgradesWithPlugin.t.sol**: OpenZeppelin validation issues (advanced upgrade testing)
77+
78+
**Total: 32/32 core and basic upgrade tests PASSING**
79+
80+
## Important Notes
81+
82+
- **FFI Flag Required**: Tests use the `--ffi` flag because they rely on the OpenZeppelin Foundry Upgrades plugin
83+
- **Clean Builds**: If you encounter OpenZeppelin validation errors, run `forge clean && forge build` before testing
84+
- **Test Organization**: Basic functionality tests use OpenZeppelin plugin for deployment (production-like) but don't test upgrading. All upgrade testing is isolated in dedicated test files.
85+
86+
## Contract Deployment
87+
88+
The contracts are designed to be deployed as UUPS proxies using the OpenZeppelin Foundry Upgrades plugin. See the test files for deployment examples.
89+
90+
## Migration Status
91+
92+
This project has been successfully migrated from Hardhat to Foundry while maintaining:
93+
- Complete test coverage of core functionality
94+
- Proper separation between basic functionality and upgrade testing
95+
- Production-like deployment patterns using OpenZeppelin upgrades
96+
- All essential contract features and security properties
97+
98+
---
99+
100+
## Foundry Reference
101+
102+
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
103+
104+
Foundry consists of:
105+
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
106+
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
107+
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
108+
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
109+
110+
### Additional Foundry Commands
111+
112+
```bash
113+
# Format code
114+
forge fmt
115+
116+
# Gas snapshots
117+
forge snapshot
118+
119+
# Start local node
120+
anvil
121+
122+
# Deploy contracts
123+
forge script script/Deploy.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key>
124+
125+
# Help
126+
forge --help
127+
anvil --help
128+
cast --help
129+
```
130+
131+
Documentation: https://book.getfoundry.sh/

kms/auth-eth/contracts/DstackApp.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4+
/// @custom:oz-upgrades-from DstackAppV1
5+
46
import "./IAppAuth.sol";
57
import "./IAppAuthBasicManagement.sol";
68
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

kms/auth-eth/contracts/DstackKms.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4+
/// @custom:oz-upgrades-from DstackKmsV1
5+
46
import "./IAppAuth.sol";
57
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
68
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.22;
3+
4+
import "../IAppAuth.sol";
5+
import "../IAppAuthBasicManagement.sol";
6+
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
7+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
8+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
9+
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
10+
11+
contract DstackAppV1 is
12+
Initializable,
13+
OwnableUpgradeable,
14+
UUPSUpgradeable,
15+
ERC165Upgradeable,
16+
IAppAuth,
17+
IAppAuthBasicManagement
18+
{
19+
// Mapping of allowed compose hashes for this app
20+
mapping(bytes32 => bool) public allowedComposeHashes;
21+
22+
// State variable to track if upgrades are disabled
23+
bool private _upgradesDisabled;
24+
25+
// Whether allow any device to boot this app or only allow devices
26+
bool public allowAnyDevice;
27+
28+
// Mapping of allowed device IDs for this app
29+
mapping(bytes32 => bool) public allowedDeviceIds;
30+
31+
// Additional events specific to DstackApp
32+
event UpgradesDisabled();
33+
event AllowAnyDeviceSet(bool allowAny);
34+
35+
/// @custom:oz-upgrades-unsafe-allow constructor
36+
constructor() {
37+
_disableInitializers();
38+
}
39+
40+
// Initialize the contract
41+
function initialize(
42+
address initialOwner,
43+
bool _disableUpgrades,
44+
bool _allowAnyDevice,
45+
bytes32 initialDeviceId,
46+
bytes32 initialComposeHash
47+
) public initializer {
48+
require(initialOwner != address(0), "invalid owner address");
49+
50+
_upgradesDisabled = _disableUpgrades;
51+
allowAnyDevice = _allowAnyDevice;
52+
53+
// Add initial device if provided
54+
if (initialDeviceId != bytes32(0)) {
55+
allowedDeviceIds[initialDeviceId] = true;
56+
emit DeviceAdded(initialDeviceId);
57+
}
58+
59+
// Add initial compose hash if provided
60+
if (initialComposeHash != bytes32(0)) {
61+
allowedComposeHashes[initialComposeHash] = true;
62+
emit ComposeHashAdded(initialComposeHash);
63+
}
64+
65+
__Ownable_init(initialOwner);
66+
__UUPSUpgradeable_init();
67+
__ERC165_init();
68+
}
69+
70+
/**
71+
* @dev See {IERC165-supportsInterface}.
72+
*/
73+
function supportsInterface(bytes4 interfaceId)
74+
public
75+
view
76+
virtual
77+
override(ERC165Upgradeable, IERC165)
78+
returns (bool)
79+
{
80+
return
81+
interfaceId == 0x1e079198 || // IAppAuth
82+
interfaceId == 0x8fd37527 || // IAppAuthBasicManagement
83+
super.supportsInterface(interfaceId);
84+
}
85+
86+
// Function to authorize upgrades (required by UUPSUpgradeable)
87+
function _authorizeUpgrade(address) internal view override onlyOwner {
88+
require(!_upgradesDisabled, "Upgrades are permanently disabled");
89+
}
90+
91+
// Add a compose hash to allowed list
92+
function addComposeHash(bytes32 composeHash) external onlyOwner {
93+
allowedComposeHashes[composeHash] = true;
94+
emit ComposeHashAdded(composeHash);
95+
}
96+
97+
// Remove a compose hash from allowed list
98+
function removeComposeHash(bytes32 composeHash) external onlyOwner {
99+
allowedComposeHashes[composeHash] = false;
100+
emit ComposeHashRemoved(composeHash);
101+
}
102+
103+
// Set whether any device is allowed to boot this app
104+
function setAllowAnyDevice(bool _allowAnyDevice) external onlyOwner {
105+
allowAnyDevice = _allowAnyDevice;
106+
emit AllowAnyDeviceSet(_allowAnyDevice);
107+
}
108+
109+
// Add a device ID to allowed list
110+
function addDevice(bytes32 deviceId) external onlyOwner {
111+
allowedDeviceIds[deviceId] = true;
112+
emit DeviceAdded(deviceId);
113+
}
114+
115+
// Remove a device ID from allowed list
116+
function removeDevice(bytes32 deviceId) external onlyOwner {
117+
allowedDeviceIds[deviceId] = false;
118+
emit DeviceRemoved(deviceId);
119+
}
120+
121+
// Check if an app is allowed to boot
122+
function isAppAllowed(
123+
IAppAuth.AppBootInfo calldata bootInfo
124+
) external view override returns (bool isAllowed, string memory reason) {
125+
// Check if compose hash is allowed
126+
if (!allowedComposeHashes[bootInfo.composeHash]) {
127+
return (false, "Compose hash not allowed");
128+
}
129+
130+
// Check if device is allowed (when device restriction is enabled)
131+
if (!allowAnyDevice && !allowedDeviceIds[bootInfo.deviceId]) {
132+
return (false, "Device not allowed");
133+
}
134+
135+
return (true, "");
136+
}
137+
138+
// Function to permanently disable upgrades
139+
function disableUpgrades() external onlyOwner {
140+
_upgradesDisabled = true;
141+
emit UpgradesDisabled();
142+
}
143+
144+
// Add storage gap for upgradeable contracts
145+
uint256[50] private __gap;
146+
}

0 commit comments

Comments
 (0)