Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
439 changes: 361 additions & 78 deletions solidity/contracts/bridge/Bridge.sol

Large diffs are not rendered by default.

65 changes: 61 additions & 4 deletions solidity/contracts/bridge/BridgeGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ contract BridgeGovernance is Ownable {
// governanceDelays[2] -> governanceDelayChangeInitiated
uint256[3] public governanceDelays;

/// @notice The minimum allowed value for the governance delay. Prevents
/// setting the delay to zero or near-zero values that would
/// effectively disable timelocked governance parameter updates.
uint256 public constant MIN_GOVERNANCE_DELAY = 3600; // 1 hour

uint256 public bridgeGovernanceTransferChangeInitiated;
address internal newBridgeGovernance;

Expand Down Expand Up @@ -289,6 +294,10 @@ contract BridgeGovernance is Ownable {
event TreasuryUpdated(address treasury);

constructor(Bridge _bridge, uint256 _governanceDelay) {
require(
_governanceDelay >= MIN_GOVERNANCE_DELAY,
"Initial governance delay must be >= minimum"
);
bridge = _bridge;
governanceDelays[0] = _governanceDelay;
}
Expand All @@ -305,6 +314,26 @@ contract BridgeGovernance is Ownable {
bridge.setVaultStatus(vault, isTrusted);
}

/// @notice Sets canonical migration debt vault used by Bridge reveal guard.
/// @param vault Address of trusted migration debt vault. Can be zero to
/// disable canonical reveal guard checks.
function setMigrationDebtVault(address vault) external onlyOwner {
bridge.setMigrationDebtVault(vault);
}

/// @notice Atomically rotates canonical migration debt vault and untrusts
/// the previous canonical vault.
/// @param newVault Address of new trusted migration debt vault. Can be
/// zero to disable canonical reveal guard checks.
/// @param previousVault Canonical migration debt vault expected before
/// rotation.
function rotateMigrationDebtVault(address newVault, address previousVault)
external
onlyOwner
{
bridge.rotateMigrationDebtVault(newVault, previousVault);
}

/// @notice Allows the Governance to mark the given address as trusted
/// or no longer trusted SPV maintainer. Addresses are not trusted
/// as SPV maintainers by default.
Expand All @@ -317,16 +346,44 @@ contract BridgeGovernance is Ownable {
bridge.setSpvMaintainerStatus(spvMaintainer, isTrusted);
}

/// @notice Forwards ETH rescue to the Bridge. Required when fraud-challenge
/// deposit refunds (bounded-gas low-level call, unchecked return)
/// leave ETH custodied in the Bridge with no other path out.
/// @param recipient Address that receives the rescued ETH.
/// @param amount Amount of ETH (in wei) to transfer.
function recoverETH(address payable recipient, uint256 amount)
external
onlyOwner
{
bridge.recoverETH(recipient, amount);
}

/// @notice Seeds missing Bridge fraud-challenge escrow introduced by the
/// upgrade that added the aggregate escrow counter.
/// @param preUpgradeOpenEscrow Sum of unresolved pre-upgrade fraud-challenge
/// deposits at the time this call is executed.
function seedFraudChallengeEscrow(uint256 preUpgradeOpenEscrow)
external
onlyOwner
{
bridge.seedFraudChallengeEscrow(preUpgradeOpenEscrow);
}

/// @notice Begins the governance delay update process.
/// @dev Can be called only by the contract owner. The event that informs about
/// the start of the governance delay was skipped on purpose to trim
/// the contract size. All the params inside of the `governanceDelays`
/// array are public and can be easily fetched.
/// @dev Can be called only by the contract owner. The new governance delay
/// must be at least `MIN_GOVERNANCE_DELAY`. The event that informs
/// about the start of the governance delay was skipped on purpose to
/// trim the contract size. All the params inside of the
/// `governanceDelays` array are public and can be easily fetched.
/// @param _newGovernanceDelay New governance delay
function beginGovernanceDelayUpdate(uint256 _newGovernanceDelay)
external
onlyOwner
{
require(
_newGovernanceDelay >= MIN_GOVERNANCE_DELAY,
"New governance delay must be >= minimum"
);
governanceDelays[1] = _newGovernanceDelay;
/* solhint-disable not-rely-on-time */
governanceDelays[2] = block.timestamp;
Expand Down
62 changes: 61 additions & 1 deletion solidity/contracts/bridge/BridgeState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,27 @@ library BridgeState {
// governance wiring; changing it afterwards requires a dedicated
// upgrade path of the Bridge implementation.
address rebateStaking;
// Canonical migration debt vault used by non-migration reveal guard.
// Optional and set through governance to enable global migration
// revealer isolation checks.
// Upgrade note: this field consumed one reserved slot, reducing
// `__gap` from 48 to 47 for storage-layout compatibility.
address migrationDebtVault;
// Reserved storage space in case we need to add more variables.
// The convention from OpenZeppelin suggests the storage space should
// add up to 50 slots. Here we want to have more slots as there are
// planned upgrades of the Bridge contract. If more entires are added to
// the struct in the upcoming versions we need to reduce the array size.
// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
// slither-disable-next-line unused-state
uint256[48] __gap;
uint256[47] __gap;
/// @notice Sum of `depositAmount` across currently-open fraud
/// challenges, used to keep `recoverETH` scoped to
/// non-escrowed ETH.
uint256 openFraudChallengeEscrow;
/// @notice True once governance has accounted for pre-upgrade open
/// fraud challenges in `openFraudChallengeEscrow`.
bool fraudChallengeEscrowSeeded;
}

event DepositParametersUpdated(
Expand Down Expand Up @@ -443,6 +456,27 @@ library BridgeState {
"Deposit transaction max fee must be greater than zero"
);

// Sweep solvency invariant: a deposit at the dust-threshold floor
// pays the treasury fee plus the per-deposit sweep tx fee out of
// its own value. `DepositSweep.submitDepositSweepProof` performs
// `amount - treasuryFee - depositTxFee` and relies on the dust
// threshold to prevent that subtraction from underflowing.
// `treasuryFee = amount / depositTreasuryFeeDivisor` when the
// divisor is non-zero (see `Deposit.sol:420`), so the worst case
// at `amount == depositDustThreshold` is
// `depositDustThreshold / depositTreasuryFeeDivisor`. Enforce
// that the dust threshold strictly exceeds the worst-case
// treasury+tx fee at parameter-set time so a misconfigured
// divisor cannot strand revealed deposits at sweep time.
if (_depositTreasuryFeeDivisor > 0) {
require(
_depositDustThreshold >
(_depositDustThreshold / _depositTreasuryFeeDivisor) +
_depositTxMaxFee,
"Deposit dust threshold must cover treasury and TX max fee"
);
}

self.depositDustThreshold = _depositDustThreshold;
self.depositTreasuryFeeDivisor = _depositTreasuryFeeDivisor;
self.depositTxMaxFee = _depositTxMaxFee;
Expand Down Expand Up @@ -630,6 +664,7 @@ library BridgeState {
/// - Moving funds timeout reset delay must be greater than zero,
/// - Moving funds timeout must be greater than the moving funds
/// timeout reset delay,
/// - Moving funds timeout slashing amount must be greater than zero,
/// - Moving funds timeout notifier reward multiplier must be in the
/// range [0, 100],
/// - Moved funds sweep transaction max total fee must be greater than zero,
Expand Down Expand Up @@ -671,6 +706,11 @@ library BridgeState {
"Moving funds timeout must be greater than its reset delay"
);

require(
_movingFundsTimeoutSlashingAmount > 0,
"Moving funds timeout slashing amount must be greater than zero"
);

require(
_movingFundsTimeoutNotifierRewardMultiplier <= 100,
"Moving funds timeout notifier reward multiplier must be in the range [0, 100]"
Expand Down Expand Up @@ -743,6 +783,8 @@ library BridgeState {
// i.e. the period when the wallet remains in the Closing state
// and can be subject of deposit fraud challenges.
/// @dev Requirements:
/// - Wallet creation period must be greater than zero,
/// - Wallet maximum age must be greater than zero,
/// - Wallet maximum BTC balance must be greater than the wallet
/// minimum BTC balance,
/// - Wallet maximum BTC transfer must be greater than zero,
Expand All @@ -757,6 +799,14 @@ library BridgeState {
uint64 _walletMaxBtcTransfer,
uint32 _walletClosingPeriod
) internal {
require(
_walletCreationPeriod > 0,
"Wallet creation period must be greater than zero"
);
require(
_walletMaxAge > 0,
"Wallet maximum age must be greater than zero"
);
require(
_walletCreationMaxBtcBalance > _walletCreationMinBtcBalance,
"Wallet creation maximum BTC balance must be greater than the creation minimum BTC balance"
Expand Down Expand Up @@ -804,15 +854,25 @@ library BridgeState {
/// the notifier reward from the staking contact the notifier of
/// a fraud receives. The value must be in the range [0, 100].
/// @dev Requirements:
/// - Fraud challenge deposit amount must be greater than zero,
/// - Fraud challenge defeat timeout must be greater than 0,
/// - Fraud notifier reward multiplier must be in the range [0, 100].
/// Note: `_fraudSlashingAmount` is intentionally unbounded on the
/// low end; the `DisableFraudChallenges` deploy flow relies on
/// setting it to zero to signal that fraud challenges are
/// disabled.
function updateFraudParameters(
Storage storage self,
uint96 _fraudChallengeDepositAmount,
uint32 _fraudChallengeDefeatTimeout,
uint96 _fraudSlashingAmount,
uint32 _fraudNotifierRewardMultiplier
) internal {
require(
_fraudChallengeDepositAmount > 0,
"Fraud challenge deposit amount must be greater than zero"
);

require(
_fraudChallengeDefeatTimeout > 0,
"Fraud challenge defeat timeout must be greater than zero"
Expand Down
Loading
Loading