diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index 64cdcfea..8c93795a 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -563,7 +563,11 @@ contract Bootstrap is return; } // bootstrapped = true is only actioned by the clientchaingateway after upgrade - // so no need to check for that here + // so no need to check for that here but better to be safe. + if (bootstrapped) { + emit BootstrappedAlready(); + return; + } try ICustomProxyAdmin(customProxyAdmin).changeImplementation( // address(this) is storage address and not logic address. so it is a proxy. ITransparentUpgradeableProxy(address(this)), @@ -575,7 +579,6 @@ contract Bootstrap is // to allow retries, never fail emit BootstrapUpgradeFailed(); } - emit Bootstrapped(); } /// @notice Sets a new client chain gateway logic and its initialization data. diff --git a/src/storage/BootstrapStorage.sol b/src/storage/BootstrapStorage.sol index aee22259..c7d6b85f 100644 --- a/src/storage/BootstrapStorage.sol +++ b/src/storage/BootstrapStorage.sol @@ -236,6 +236,11 @@ contract BootstrapStorage is GatewayStorage { /// intentionally to prevent blocking the system. event BootstrapUpgradeFailed(); + /// @notice Emitted when the contract is already bootstrapped. + /// @dev This event is triggered when the contract is already bootstrapped and an attempt is made to bootstrap it + /// again. It is not an error intentionally to prevent blocking the system. + event BootstrappedAlready(); + /// @notice Emitted when the client chain gateway logic + implementation are updated. /// @dev This event is triggered whenever the client chain gateway logic and implementation are updated. It may be /// used, before bootstrapping is complete, to upgrade the client chain gateway logic for upgrades or other bugs. diff --git a/test/foundry/unit/Bootstrap.t.sol b/test/foundry/unit/Bootstrap.t.sol index 3f560910..f4129bda 100644 --- a/test/foundry/unit/Bootstrap.t.sol +++ b/test/foundry/unit/Bootstrap.t.sol @@ -58,6 +58,7 @@ contract BootstrapTest is Test { address exocoreValidatorSet = vm.addr(uint256(0x8)); address undeployedExocoreGateway = vm.addr(uint256(0x9)); address undeployedExocoreLzEndpoint = vm.addr(uint256(0xb)); + address constant lzActor = address(0x20); IVault vaultImplementation; IExoCapsule capsuleImplementation; @@ -889,36 +890,35 @@ contract BootstrapTest is Test { } function test12_MarkBootstrapped() public { + // go after spawn time vm.warp(spawnTime + 1); - vm.startPrank(address(0x20)); + _markBootstrapped(1, true); + } + + function _markBootstrapped(uint64 nonce, bool success) internal { + vm.startPrank(lzActor); clientChainLzEndpoint.lzReceive( - Origin(exocoreChainId, bytes32(bytes20(undeployedExocoreGateway)), uint64(1)), + Origin(exocoreChainId, bytes32(bytes20(undeployedExocoreGateway)), nonce), address(bootstrap), generateUID(1), abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, ""), bytes("") ); vm.stopPrank(); - assertTrue(bootstrap.bootstrapped()); - // ensure that it cannot be upgraded ever again. - assertTrue(bootstrap.customProxyAdmin() == address(0)); - assertTrue(proxyAdmin.bootstrapper() == address(0)); - assertTrue(bootstrap.owner() == exocoreValidatorSet); - // getDepositorsCount is no longer a function so can't check the count. - // assertTrue(bootstrap.getDepositorsCount() == 0); + if (success) { + assertTrue(bootstrap.bootstrapped()); + // no more upgrades are possible + assertTrue(bootstrap.customProxyAdmin() == address(0)); + assertTrue(proxyAdmin.bootstrapper() == address(0)); + assertTrue(bootstrap.owner() == exocoreValidatorSet); + } else { + assertFalse(bootstrap.bootstrapped()); + } } function test12_MarkBootstrapped_NotTime() public { - vm.startPrank(address(0x20)); - clientChainLzEndpoint.lzReceive( - Origin(exocoreChainId, bytes32(bytes20(undeployedExocoreGateway)), uint64(1)), - address(bootstrap), - generateUID(1), - abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, ""), - bytes("") - ); - vm.stopPrank(); - assertFalse(bootstrap.bootstrapped()); + // spawn time is 1 hour later, so this will fail. + _markBootstrapped(1, false); } function test12_MarkBootstrapped_AlreadyBootstrapped() public { @@ -940,13 +940,21 @@ contract BootstrapTest is Test { } function test12_MarkBootstrapped_DirectCall() public { - vm.startPrank(address(0x20)); + // can be any adddress but for clarity use non lz actor + vm.startPrank(address(0x21)); vm.warp(spawnTime + 2); vm.expectRevert(Errors.BootstrapLzReceiverOnlyCalledFromThis.selector); bootstrap.markBootstrapped(); vm.stopPrank(); } + function test12_MarkBootstrapped_FailThenSucceed() public { + vm.warp(spawnTime - 5); + _markBootstrapped(1, false); + vm.warp(spawnTime + 1); + _markBootstrapped(2, true); + } + function test13_OperationAllowed() public { vm.warp(spawnTime - offsetDuration); vm.startPrank(addrs[0]);