Skip to content

Commit be3a471

Browse files
authored
Merge pull request #5 from filbeam/feat/migrate-filbeam-controller
feat: add FWSS controller migration method to FilBeamOperator
2 parents 42280b7 + 08d2018 commit be3a471

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed

SPEC.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,29 @@ The Filecoin Beam (FilBeamOperator) contract is responsible for managing CDN (ca
142142
- **Purpose**: Update the authorized address for usage reporting and payment rail termination
143143
- **Events**: Emits `FilBeamOperatorControllerUpdated` event
144144

145+
**Method**: `transferFwssFilBeamController(address newController)`
146+
147+
- **Access**: Contract owner only
148+
- **Requirements**: New controller address cannot be zero address
149+
- **Purpose**: Transfer FilBeamController authorization in FWSS to a new operator contract. This is used during contract upgrades to migrate control from the current FilBeamOperator to a new FilBeamOperator instance.
150+
- **Functionality**: Calls `transferFilBeamController(newController)` on the FWSS contract, which will update the authorized caller in FWSS to the new operator address
151+
- **Events**:
152+
- Emits `FwssFilBeamControllerMigrated(address indexed previousController, address indexed newController)` from FilBeamOperator
153+
- FWSS will emit `FilBeamControllerChanged(address indexed oldController, address indexed newController)`
154+
- **Migration Flow**:
155+
1. Deploy new FilBeamOperator contract
156+
2. Call `transferFwssFilBeamController(newOperatorAddress)` on old operator contract
157+
3. FWSS authorization transfers to new operator
158+
4. Old operator can no longer call FWSS methods
159+
5. New operator can now interact with FWSS
160+
145161
#### Events
146162
- `UsageReported(uint256 indexed dataSetId, uint256 indexed fromEpoch, uint256 indexed toEpoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed)`
147163
- `CDNSettlement(uint256 indexed dataSetId, uint256 fromEpoch, uint256 toEpoch, uint256 cdnAmount)`
148164
- `CacheMissSettlement(uint256 indexed dataSetId, uint256 fromEpoch, uint256 toEpoch, uint256 cacheMissAmount)`
149165
- `PaymentRailsTerminated(uint256 indexed dataSetId)`
150166
- `FilBeamOperatorControllerUpdated(address indexed oldController, address indexed newController)`
167+
- `FwssFilBeamControllerMigrated(address indexed previousController, address indexed newController)`
151168

152169
#### Access Control
153170
- **Owner**: Address authorized to manage contract ownership and set FilBeamOperator controller

src/FilBeamOperator.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ contract FilBeamOperator is Ownable {
3737

3838
event FilBeamControllerUpdated(address indexed oldController, address indexed newController);
3939

40+
event FwssFilBeamControllerChanged(address indexed previousController, address indexed newController);
41+
4042
/// @notice Initializes the FilBeamOperator contract
4143
/// @param fwssAddress Address of the FWSS contract
4244
/// @param _paymentsAddress Address of the Payments contract
@@ -128,6 +130,19 @@ contract FilBeamOperator is Ownable {
128130
emit FilBeamControllerUpdated(oldController, _filBeamOperatorController);
129131
}
130132

133+
/// @notice Transfers the FilBeamController authorization in FWSS to a new operator
134+
/// @dev Can only be called by the contract owner. This is used during contract upgrades
135+
/// to transfer control from the current operator to a new operator contract.
136+
/// @param newController Address of the new FilBeamOperator contract
137+
function transferFwssFilBeamController(address newController) external onlyOwner {
138+
if (newController == address(0)) revert InvalidAddress();
139+
140+
// Transfer the controller authorization in FWSS to the new operator
141+
IFWSS(fwssContractAddress).transferFilBeamController(newController);
142+
143+
emit FwssFilBeamControllerChanged(address(this), newController);
144+
}
145+
131146
/// @dev Internal function to record usage for a single data set
132147
/// @param dataSetId The data set ID
133148
/// @param toEpoch The epoch number to record usage for

src/interfaces/IFWSS.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ interface IFWSS {
2222
function terminateCDNPaymentRails(uint256 dataSetId) external;
2323

2424
function usdfcTokenAddress() external view returns (address);
25+
26+
function transferFilBeamController(address newController) external;
2527
}

src/mocks/MockFWSS.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ contract MockFWSS is IFWSS {
1919

2020
event PaymentRailsSettled(uint256 indexed dataSetId, uint256 cdnAmount, uint256 cacheMissAmount);
2121
event PaymentRailsTerminated(uint256 indexed dataSetId);
22+
event FilBeamControllerChanged(address indexed oldController, address indexed newController);
2223

2324
error UnauthorizedCaller();
2425

@@ -77,4 +78,11 @@ contract MockFWSS is IFWSS {
7778
function setDataSetInfo(uint256 dataSetId, DataSetInfo memory info) external {
7879
dataSetInfos[dataSetId] = info;
7980
}
81+
82+
function transferFilBeamController(address newController) external onlyAuthorized {
83+
require(newController != address(0), "Zero address");
84+
address oldController = authorizedCaller;
85+
authorizedCaller = newController;
86+
emit FilBeamControllerChanged(oldController, newController);
87+
}
8088
}

test/FilBeamOperator.t.sol

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ contract FilBeamOperatorTest is Test {
3535

3636
event CacheMissSettlement(uint256 indexed dataSetId, uint256 cacheMissAmount);
3737

38+
event FwssFilBeamControllerChanged(address indexed previousController, address indexed newController);
39+
40+
event FilBeamControllerChanged(address indexed oldController, address indexed newController);
41+
3842
event PaymentRailsTerminated(uint256 indexed dataSetId);
3943

4044
event FilBeamControllerUpdated(address indexed oldController, address indexed newController);
@@ -1279,4 +1283,153 @@ contract FilBeamOperatorTest is Test {
12791283
assertEq(cdnAmount, 100000, "CDN amount should still be accumulated");
12801284
assertEq(cacheMissAmount, 100000, "Cache miss amount should still be accumulated");
12811285
}
1286+
1287+
// ============ Migration Tests ============
1288+
1289+
function test_TransferFwssFilBeamController_Success() public {
1290+
address newOperator = address(0x9999);
1291+
1292+
// Verify initial state
1293+
assertEq(mockFWSS.authorizedCaller(), address(filBeam), "Initial authorized caller should be current operator");
1294+
1295+
// FWSS emits FilBeamControllerChanged event first (from inside the call)
1296+
vm.expectEmit(true, true, false, true, address(mockFWSS));
1297+
emit FilBeamControllerChanged(address(filBeam), newOperator);
1298+
1299+
// Then FilBeamOperator emits FwssFilBeamControllerChanged event
1300+
vm.expectEmit(true, true, false, true, address(filBeam));
1301+
emit FwssFilBeamControllerChanged(address(filBeam), newOperator);
1302+
1303+
// Call as owner
1304+
vm.prank(owner);
1305+
filBeam.transferFwssFilBeamController(newOperator);
1306+
1307+
// Verify FWSS authorization was transferred
1308+
assertEq(mockFWSS.authorizedCaller(), newOperator, "FWSS authorized caller should be new operator");
1309+
}
1310+
1311+
function test_TransferFwssFilBeamController_RevertNonOwner() public {
1312+
address newOperator = address(0x9999);
1313+
1314+
// Try as non-owner (controller)
1315+
vm.prank(filBeamOperatorController);
1316+
vm.expectRevert();
1317+
filBeam.transferFwssFilBeamController(newOperator);
1318+
1319+
// Try as random user
1320+
vm.prank(user1);
1321+
vm.expectRevert();
1322+
filBeam.transferFwssFilBeamController(newOperator);
1323+
1324+
// Verify authorization wasn't changed
1325+
assertEq(mockFWSS.authorizedCaller(), address(filBeam), "Authorized caller should remain unchanged");
1326+
}
1327+
1328+
function test_TransferFwssFilBeamController_RevertZeroAddress() public {
1329+
// Try with zero address
1330+
vm.prank(owner);
1331+
vm.expectRevert(InvalidAddress.selector);
1332+
filBeam.transferFwssFilBeamController(address(0));
1333+
1334+
// Verify authorization wasn't changed
1335+
assertEq(mockFWSS.authorizedCaller(), address(filBeam), "Authorized caller should remain unchanged");
1336+
}
1337+
1338+
function test_TransferFwssFilBeamController_OldOperatorCannotCallAfterMigration() public {
1339+
address newOperator = address(0x9999);
1340+
1341+
// First record some usage to verify old operator works
1342+
vm.prank(filBeamOperatorController);
1343+
filBeam.recordUsageRollups(
1344+
200, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(1000), _singleUint256Array(500)
1345+
);
1346+
1347+
// Migrate to new operator
1348+
vm.prank(owner);
1349+
filBeam.transferFwssFilBeamController(newOperator);
1350+
1351+
// Old operator should no longer be able to call FWSS methods (settle will fail)
1352+
vm.expectRevert(MockFWSS.UnauthorizedCaller.selector);
1353+
filBeam.settleCDNPaymentRails(_singleUint256Array(DATA_SET_ID_1));
1354+
}
1355+
1356+
function test_TransferFwssFilBeamController_NewOperatorCanCallAfterMigration() public {
1357+
// Deploy a new FilBeamOperator instance to act as the new operator
1358+
FilBeamOperator newOperator = new FilBeamOperator(
1359+
address(mockFWSS),
1360+
address(mockPayments),
1361+
CDN_RATE_PER_BYTE,
1362+
CACHE_MISS_RATE_PER_BYTE,
1363+
filBeamOperatorController
1364+
);
1365+
1366+
// Record usage with old operator
1367+
vm.prank(filBeamOperatorController);
1368+
filBeam.recordUsageRollups(
1369+
200, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(1000), _singleUint256Array(500)
1370+
);
1371+
1372+
// Migrate to new operator
1373+
vm.prank(owner);
1374+
filBeam.transferFwssFilBeamController(address(newOperator));
1375+
1376+
// New operator should be able to record and settle
1377+
vm.prank(filBeamOperatorController);
1378+
newOperator.recordUsageRollups(
1379+
300, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(2000), _singleUint256Array(1000)
1380+
);
1381+
1382+
// New operator can settle
1383+
newOperator.settleCDNPaymentRails(_singleUint256Array(DATA_SET_ID_1));
1384+
1385+
// Verify settlement was successful
1386+
assertGt(mockFWSS.getSettlementsCount(), 0, "Settlement should have occurred");
1387+
}
1388+
1389+
function test_TransferFwssFilBeamController_IntegrationFlow() public {
1390+
// Deploy new operator
1391+
FilBeamOperator newOperator = new FilBeamOperator(
1392+
address(mockFWSS),
1393+
address(mockPayments),
1394+
CDN_RATE_PER_BYTE,
1395+
CACHE_MISS_RATE_PER_BYTE,
1396+
filBeamOperatorController
1397+
);
1398+
1399+
// 1. Old operator records usage
1400+
vm.prank(filBeamOperatorController);
1401+
filBeam.recordUsageRollups(
1402+
200, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(1000), _singleUint256Array(500)
1403+
);
1404+
1405+
// 2. Settle partially with old operator (this works)
1406+
filBeam.settleCDNPaymentRails(_singleUint256Array(DATA_SET_ID_1));
1407+
uint256 settlementCountBefore = mockFWSS.getSettlementsCount();
1408+
1409+
// 3. Old operator records more usage (to have accumulated amount after migration)
1410+
vm.prank(filBeamOperatorController);
1411+
filBeam.recordUsageRollups(
1412+
250, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(5000), _singleUint256Array(2500)
1413+
);
1414+
1415+
// 4. Migrate to new operator
1416+
vm.prank(owner);
1417+
filBeam.transferFwssFilBeamController(address(newOperator));
1418+
1419+
// 5. New operator records more usage
1420+
vm.prank(filBeamOperatorController);
1421+
newOperator.recordUsageRollups(
1422+
300, _singleUint256Array(DATA_SET_ID_1), _singleUint256Array(2000), _singleUint256Array(1000)
1423+
);
1424+
1425+
// 6. New operator settles
1426+
newOperator.settleCDNPaymentRails(_singleUint256Array(DATA_SET_ID_1));
1427+
1428+
// Verify settlements occurred
1429+
assertGt(mockFWSS.getSettlementsCount(), settlementCountBefore, "New settlement should have occurred");
1430+
1431+
// 7. Old operator cannot settle anymore (has accumulated amount but can't settle to FWSS)
1432+
vm.expectRevert(MockFWSS.UnauthorizedCaller.selector);
1433+
filBeam.settleCDNPaymentRails(_singleUint256Array(DATA_SET_ID_1));
1434+
}
12821435
}

0 commit comments

Comments
 (0)