Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bootstrap): do not fail mark bootstrap #90

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions script/11_SetPeers.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity ^0.8.19;

import {Bootstrap} from "../src/core/Bootstrap.sol";
import {ExocoreGateway} from "../src/core/ExocoreGateway.sol";
import {GatewayStorage} from "../src/storage/GatewayStorage.sol";

import {BaseScript} from "./BaseScript.sol";
import "forge-std/Script.sol";
Expand Down Expand Up @@ -53,12 +54,9 @@ contract SetPeersAndUpgrade is BaseScript {

vm.selectFork(exocore);
vm.startBroadcast(exocoreValidatorSet.privateKey);
// fund the gateway
if (exocoreGatewayAddr.balance < 1 ether) {
(bool sent,) = exocoreGatewayAddr.call{value: 1 ether}("");
require(sent, "Failed to send Ether");
}
gateway.markBootstrapOnAllChains();
uint256 nativeFee =
exocoreGateway.quote(clientChainId, abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, ""));
exocoreGateway.markBootstrap{value: nativeFee}(clientChainId);
}

}
9 changes: 8 additions & 1 deletion script/14_CorrectBootstrapErrors.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ contract CorrectBootstrapErrors is BaseScript {
address wstETH;
address proxyAddress;
address proxyAdmin;
address clientGatewayLogic;
bytes initialization;

function setUp() public virtual override {
// load keys
Expand Down Expand Up @@ -62,6 +64,9 @@ contract CorrectBootstrapErrors is BaseScript {
require(address(vaultImplementation) != address(0), "vault implementation should not be empty");
vaultBeacon = UpgradeableBeacon(stdJson.readAddress(deployed, ".clientChain.vaultBeacon"));
require(address(vaultBeacon) != address(0), "vault beacon should not be empty");
clientGatewayLogic = stdJson.readAddress(deployed, ".clientChain.clientGatewayLogic");
require(clientGatewayLogic != address(0), "client gateway should not be empty");
initialization = abi.encodeCall(ClientChainGateway.initialize, (payable(exocoreValidatorSet.addr)));
}

function run() public {
Expand All @@ -81,7 +86,9 @@ contract CorrectBootstrapErrors is BaseScript {
block.timestamp + 168 hours,
2 seconds,
emptyList,
address(proxyAdmin)
address(proxyAdmin),
address(clientGateway),
initialization
)
);
proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxyAddress), address(bootstrapLogic), data);
Expand Down
36 changes: 18 additions & 18 deletions script/7_DeployBootstrap.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ contract DeployBootstrapOnly is BaseScript {
Bootstrap bootstrapLogic = new Bootstrap(
address(clientChainLzEndpoint), exocoreChainId, address(vaultBeacon), address(beaconProxyBytecode)
);
// client chain constructor (upgrade details)
capsuleImplementation = new ExoCapsule();
capsuleBeacon = new UpgradeableBeacon(address(capsuleImplementation));
ClientChainGateway clientGatewayLogic = new ClientChainGateway(
address(clientChainLzEndpoint),
exocoreChainId,
address(beaconOracle),
address(vaultBeacon),
address(capsuleBeacon),
address(beaconProxyBytecode)
);
// then the client chain initialization
address[] memory emptyList;
bytes memory initialization =
abi.encodeWithSelector(clientGatewayLogic.initialize.selector, exocoreValidatorSet.addr, emptyList);
// bootstrap implementation
Bootstrap bootstrap = Bootstrap(
payable(
Expand All @@ -75,7 +90,9 @@ contract DeployBootstrapOnly is BaseScript {
block.timestamp + 168 hours,
2 seconds,
whitelistTokens, // vault is auto deployed
address(proxyAdmin)
address(proxyAdmin),
address(clientGatewayLogic),
initialization
)
)
)
Expand All @@ -86,23 +103,6 @@ contract DeployBootstrapOnly is BaseScript {
// initialize proxyAdmin with bootstrap address
proxyAdmin.initialize(address(bootstrap));

// now, focus on the client chain constructor
capsuleImplementation = new ExoCapsule();
capsuleBeacon = new UpgradeableBeacon(address(capsuleImplementation));
ClientChainGateway clientGatewayLogic = new ClientChainGateway(
address(clientChainLzEndpoint),
exocoreChainId,
address(beaconOracle),
address(vaultBeacon),
address(capsuleBeacon),
address(beaconProxyBytecode)
);
// then the client chain initialization
address[] memory emptyList;
bytes memory initialization =
abi.encodeWithSelector(clientGatewayLogic.initialize.selector, exocoreValidatorSet.addr, emptyList);
bootstrap.setClientChainGatewayLogic(address(clientGatewayLogic), initialization);

vm.stopBroadcast();

string memory clientChainContracts = "clientChainContracts";
Expand Down
4 changes: 3 additions & 1 deletion script/integration/1_DeployBootstrap.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ contract DeployContracts is Script {
block.timestamp + 3 minutes,
1 seconds,
whitelistTokens,
address(proxyAdmin)
address(proxyAdmin),
address(0x1), // these values don't matter for the localnet generate.js test
bytes("123456")
)
)
)
Expand Down
42 changes: 30 additions & 12 deletions src/core/Bootstrap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ contract Bootstrap is
uint256 spawnTime_,
uint256 offsetDuration_,
address[] calldata whitelistTokens_,
address customProxyAdmin_
address customProxyAdmin_,
address clientChainGatewayLogic_,
bytes calldata clientChainInitializationData_
) external initializer {
if (owner == address(0)) {
revert Errors.ZeroAddress();
Expand All @@ -83,6 +85,7 @@ contract Bootstrap is

customProxyAdmin = customProxyAdmin_;
bootstrapped = false;
_setClientChainGatewayLogic(clientChainGatewayLogic_, clientChainInitializationData_);

// msg.sender is not the proxy admin but the transparent proxy itself, and hence,
// cannot be used here. we must require a separate owner. since the Exocore validator
Expand Down Expand Up @@ -546,30 +549,32 @@ contract Bootstrap is
/// initialization data must be set. The contract must not have been bootstrapped before.
/// Once it is marked bootstrapped, the implementation of the contract is upgraded to the
/// client chain gateway logic contract.
/// @dev This call can never fail, since such failures are not handled by ExocoreGateway.
function markBootstrapped() public onlyCalledFromThis whenNotPaused {
// whenNotPaused is applied so that the upgrade does not proceed without unpausing it.
// LZ checks made so far include:
// lzReceive called by endpoint
// correct address on remote (peer match)
// chainId match
// nonce match, which requires that inbound nonce is uint64(1).
// TSS checks are not super clear since they can be set by anyone
// but at this point that does not matter since it is not fully implemented anyway.
if (block.timestamp < spawnTime) {
revert Errors.BootstrapNotSpawnTime();
// technically never possible unless the block producer does some time-based shenanigans.
emit BootstrapNotTimeYet();
return;
}
if (bootstrapped) {
revert Errors.BootstrapAlreadyBootstrapped();
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
}
if (clientChainGatewayLogic == address(0)) {
revert Errors.ZeroAddress();
}
ICustomProxyAdmin(customProxyAdmin).changeImplementation(
// bootstrapped = true is only actioned by the clientchaingateway after upgrade
// so no need to check for that here
try ICustomProxyAdmin(customProxyAdmin).changeImplementation(
// address(this) is storage address and not logic address. so it is a proxy.
ITransparentUpgradeableProxy(address(this)),
clientChainGatewayLogic,
clientChainInitializationData
);
) {
emit Bootstrapped();
} catch {
// to allow retries, never fail
emit BootstrapUpgradeFailed();
}
emit Bootstrapped();
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -584,9 +589,22 @@ contract Bootstrap is
public
onlyOwner
{
_setClientChainGatewayLogic(_clientChainGatewayLogic, _clientChainInitializationData);
}

/// @dev Internal version of `setClientChainGatewayLogic`.
/// @param _clientChainGatewayLogic The address of the new client chain gateway logic
/// contract.
/// @param _clientChainInitializationData The initialization data to be used when setting up
/// the new logic contract.
function _setClientChainGatewayLogic(
address _clientChainGatewayLogic,
bytes calldata _clientChainInitializationData
) internal {
if (_clientChainGatewayLogic == address(0)) {
revert Errors.ZeroAddress();
}
// selector is 4 bytes long
if (_clientChainInitializationData.length < 4) {
revert Errors.BootstrapClientChainDataMalformed();
}
Expand Down
37 changes: 14 additions & 23 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,20 @@ contract ExocoreGateway is
_unpause();
}

/// @notice Marks the bootstrap on all chains.
/// @dev This function obtains a list of client chain ids from the precompile, and then
/// sends a `REQUEST_MARK_BOOTSTRAP` to all of them. In response, the Bootstrap contract
/// on those chains should upgrade itself to the ClientChainGateway contract.
/// This function should be the first to be called after the LZ infrastructure is ready.
// TODO: call this function automatically, either within the initializer (which requires
// setPeer) or be triggered by Golang after the contract is deployed.
// For manual calls, this function should be called immediately after deployment and
// then never needs to be called again.
function markBootstrapOnAllChains() public whenNotPaused nonReentrant {
(bool success, uint32[] memory chainIndices) = ASSETS_CONTRACT.getClientChains();
if (!success) {
revert Errors.ExocoreGatewayFailedToGetClientChainIds();
}
for (uint256 i = 0; i < chainIndices.length; ++i) {
uint32 chainIndex = chainIndices[i];
if (!chainToBootstrapped[chainIndex]) {
_sendInterchainMsg(chainIndex, Action.REQUEST_MARK_BOOTSTRAP, "", true);
// TODO: should this be marked only upon receiving a response?
chainToBootstrapped[chainIndex] = true;
emit BootstrapRequestSent(chainIndex);
}
}
/// @notice Sends a request to mark the bootstrap on a chain.
/// @param chainIndex The index of the chain.
/// @dev This function is useful if the bootstrap failed on a chain and needs to be retried.
function markBootstrap(uint32 chainIndex) public payable whenNotPaused nonReentrant {
_markBootstrap(chainIndex);
}

/// @dev Internal function to mark the bootstrap on a chain.
/// @param chainIndex The index of the chain.
function _markBootstrap(uint32 chainIndex) internal {
// we don't track that a request was sent to a chain to allow for retrials
// if the transaction fails on the destination chain
_sendInterchainMsg(chainIndex, Action.REQUEST_MARK_BOOTSTRAP, "", false);
emit BootstrapRequestSent(chainIndex);
}

/// @inheritdoc IExocoreGateway
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/IExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore {
string[] calldata metaData
) external payable;

/// @notice Marks the network as bootstrapped, on the client chain.
/// @dev Causes an upgrade of the Bootstrap contract to the ClientChainGateway contract.
/// @dev Only works if LZ infrastructure is set up and SetPeer has been called.
/// @dev This is payable because it requires a fee to be paid to LZ.
/// @param clientChainId The LayerZero chain id of the client chain.
function markBootstrap(uint32 clientChainId) external payable;

}
6 changes: 0 additions & 6 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,6 @@ library Errors {
/// @dev Bootstrap: no ether required for delegation/undelegation
error BootstrapNoEtherForDelegation();

/// @dev Bootstrap: not yet in the bootstrap time
error BootstrapNotSpawnTime();

/// @dev Bootstrap: not yet bootstrapped
error BootstrapAlreadyBootstrapped();

/// @dev Bootstrap: client chain initialization data is malformed
error BootstrapClientChainDataMalformed();

Expand Down
9 changes: 9 additions & 0 deletions src/storage/BootstrapStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ contract BootstrapStorage is GatewayStorage {
/// trigger this event.
event Bootstrapped();

/// @notice Emitted when a mark bootstrap call is received before the spawn time.
/// @dev This event is triggered when a mark bootstrap call is received before the spawn time.
event BootstrapNotTimeYet();

/// @notice Emitted if the bootstrap upgrade to client chain gateway fails.
/// @dev This event is triggered if the upgrade from Bootstrap to Client Chain Gateway fails. It is not an error
/// intentionally to prevent blocking the system.
event BootstrapUpgradeFailed();

/// @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.
Expand Down
4 changes: 0 additions & 4 deletions src/storage/ExocoreGatewayStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ contract ExocoreGatewayStorage is GatewayStorage {
/// @dev The msg.value for all the destination chains.
uint128 internal constant DESTINATION_MSG_VALUE = 0;

/// @notice A mapping from client chain IDs to whether the chain has been bootstrapped.
/// @dev Used to ensure no repeated bootstrap requests are sent.
mapping(uint32 clienChainId => bool) public chainToBootstrapped;

/// @notice Emitted when a precompile call fails.
/// @param precompile Address of the precompile contract.
/// @param nonce The LayerZero nonce
Expand Down
Loading
Loading