Skip to content
Draft
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
69 changes: 58 additions & 11 deletions solidity/contracts/bridge/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ contract Bridge is
address oldRebateStaking,
address newRebateStaking
);
event PegKeeperUpdated(address pegKeeper, bool allowed);

/// @notice Emitted when a deposit's vault field is corrected via governance.
/// @dev This event is used for transparency when fixing deposits that were
Expand Down Expand Up @@ -357,6 +358,8 @@ contract Bridge is
/// keccak256(fundingTxHash || fundingOutputIndex) where:
/// - fundingTxHash (little-endian): 0x7ee3fcd03309745af5f35f07572b68affb3f551d8de70b194773644a47c9bb9c
/// - fundingOutputIndex: 1
// Upgrade-only guards omit revert strings to keep Bridge under the size cap.
// solhint-disable reason-string
function initializeV2_FixVaultZeroDeposit() external reinitializer(2) {
// Deposit key: keccak256(fundingTxHash || fundingOutputIndex)
uint256 depositKey = 0xf3bc9cd6f46f4c206bc8711e40bb5692e8fe5f0ac4d4da0a709dc71bb751c98a;
Expand All @@ -366,9 +369,9 @@ contract Bridge is

// Safety checks
Deposit.DepositRequest storage deposit = self.deposits[depositKey];
require(deposit.revealedAt != 0, "Deposit not revealed");
require(deposit.vault == address(0), "Vault already set");
require(deposit.sweptAt == 0, "Deposit already swept");
require(deposit.revealedAt != 0);
require(deposit.vault == address(0));
require(deposit.sweptAt == 0);

// Fix the vault
deposit.vault = tbtcVault;
Expand All @@ -390,23 +393,51 @@ contract Bridge is
{
address oldRebateStaking = self.rebateStaking;

require(
oldRebateStaking != newRebateStaking,
"Rebate staking unchanged"
);
require(oldRebateStaking != newRebateStaking);

if (newRebateStaking != address(0)) {
require(
newRebateStaking.code.length > 0,
"Rebate staking must be a contract"
);
require(newRebateStaking.code.length > 0);
}

self.rebateStaking = newRebateStaking;

emit RebateStakingRepaired(oldRebateStaking, newRebateStaking);
}

// solhint-enable reason-string

/// @notice Configures an initial peg keeper during a proxy upgrade.
/// @param initialPegKeeper The initial peg keeper address.
/// @dev Uses reinitializer(6) to allow a one-time configuration of the peg
/// keeper. This function can only be called once per proxy deployment.
function initializeV6_ConfigurePegKeeper(address initialPegKeeper)
external
reinitializer(6)
{
// solhint-disable-next-line no-inline-assembly
assembly {
if or(
iszero(initialPegKeeper),
iszero(
eq(
caller(),
sload(
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
)
)
)
) {
revert(0, 0)
}
}

self.updatePegKeeper(initialPegKeeper, true);

emit RebateStakingRepaired(self.rebateStaking, address(0));
self.rebateStakingDisabled = true;
self.rebateStaking = address(0);
}

/// @notice Used by the depositor to reveal information about their P2(W)SH
/// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
/// wallet listens for revealed deposit events and may decide to
Expand Down Expand Up @@ -1611,6 +1642,17 @@ contract Bridge is
self.updateTreasury(treasury);
}

/// @notice Updates a peg keeper status. Peg keepers are exempt from
/// deposit and redemption treasury fees.
/// @param pegKeeper Peg keeper address.
/// @param allowed True if the address should be allowed, false otherwise.
function updatePegKeeper(address pegKeeper, bool allowed)
external
onlyGovernance
{
self.updatePegKeeper(pegKeeper, allowed);
}

/// @notice Collection of all revealed deposits indexed by
/// keccak256(fundingTxHash | fundingOutputIndex).
/// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
Expand Down Expand Up @@ -2037,6 +2079,11 @@ contract Bridge is
return self.rebateStaking;
}

/// @return True if the address is allowed as a peg keeper.
function isPegKeeper(address pegKeeper) external view returns (bool) {
return self.pegKeepers[pegKeeper];
}

/// @notice Sets the redemption watchtower address.
/// @param redemptionWatchtower Address of the redemption watchtower.
/// @dev Requirements:
Expand Down
36 changes: 36 additions & 0 deletions solidity/contracts/bridge/BridgeGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ contract BridgeGovernance is Ownable {
using BridgeGovernanceParameters for BridgeGovernanceParameters.WalletData;
using BridgeGovernanceParameters for BridgeGovernanceParameters.FraudData;
using BridgeGovernanceParameters for BridgeGovernanceParameters.TreasuryData;
using BridgeGovernanceParameters for BridgeGovernanceParameters.PegKeeperData;

BridgeGovernanceParameters.DepositData internal depositData;
BridgeGovernanceParameters.RedemptionData internal redemptionData;
BridgeGovernanceParameters.MovingFundsData internal movingFundsData;
BridgeGovernanceParameters.WalletData internal walletData;
BridgeGovernanceParameters.FraudData internal fraudData;
BridgeGovernanceParameters.TreasuryData internal treasuryData;
BridgeGovernanceParameters.PegKeeperData internal pegKeeperData;

Bridge internal bridge;

Expand Down Expand Up @@ -287,6 +289,13 @@ contract BridgeGovernance is Ownable {

event TreasuryUpdateStarted(address newTreasury, uint256 timestamp);
event TreasuryUpdated(address treasury);
event PegKeeperUpdateStarted(
address pegKeeper,
bool allowed,
uint256 timestamp
);
event PegKeeperUpdateCanceled(address pegKeeper, bool allowed);
event PegKeeperUpdated(address pegKeeper, bool allowed);

constructor(Bridge _bridge, uint256 _governanceDelay) {
bridge = _bridge;
Expand Down Expand Up @@ -1762,6 +1771,33 @@ contract BridgeGovernance is Ownable {
bridge.updateTreasury(newTreasury);
}

/// @notice Begins the peg keeper status update process.
/// @dev Can be called only by the contract owner.
/// @param _pegKeeper Peg keeper address.
/// @param _allowed True if the address should be allowed, false otherwise.
function beginPegKeeperUpdate(address _pegKeeper, bool _allowed)
external
onlyOwner
{
pegKeeperData.beginPegKeeperUpdate(_pegKeeper, _allowed);
}

/// @notice Finalizes the peg keeper status update process.
/// @dev Can be called only by the contract owner, after the governance
/// delay elapses.
function finalizePegKeeperUpdate() external onlyOwner {
address pegKeeper = pegKeeperData.pegKeeper;
bool allowed = pegKeeperData.allowed;
pegKeeperData.finalizePegKeeperUpdate(governanceDelay());
bridge.updatePegKeeper(pegKeeper, allowed);
}

/// @notice Cancels a pending peg keeper status update.
/// @dev Can be called only by the contract owner.
function cancelPegKeeperUpdate() external onlyOwner {
pegKeeperData.cancelPegKeeperUpdate();
}

/// @notice Gets the governance delay parameter.
function governanceDelay() internal view returns (uint256) {
return governanceDelays[0];
Expand Down
64 changes: 64 additions & 0 deletions solidity/contracts/bridge/BridgeGovernanceParameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ library BridgeGovernanceParameters {
uint256 treasuryChangeInitiated;
}

struct PegKeeperData {
address pegKeeper;
bool allowed;
uint256 pegKeeperChangeInitiated;
}

struct DepositData {
uint64 newDepositDustThreshold;
uint256 depositDustThresholdChangeInitiated;
Expand Down Expand Up @@ -330,6 +336,13 @@ library BridgeGovernanceParameters {

event TreasuryUpdateStarted(address newTreasury, uint256 timestamp);
event TreasuryUpdated(address treasury);
event PegKeeperUpdateStarted(
address pegKeeper,
bool allowed,
uint256 timestamp
);
event PegKeeperUpdateCanceled(address pegKeeper, bool allowed);
event PegKeeperUpdated(address pegKeeper, bool allowed);

/// @notice Reverts if called before the governance delay elapses.
/// @param changeInitiatedTimestamp Timestamp indicating the beginning
Expand Down Expand Up @@ -1571,4 +1584,55 @@ library BridgeGovernanceParameters {
self.newTreasury = address(0);
self.treasuryChangeInitiated = 0;
}

/// @notice Begins the peg keeper status update process.
/// @param _pegKeeper Peg keeper address.
/// @param _allowed True if the address should be allowed, false otherwise.
function beginPegKeeperUpdate(
PegKeeperData storage self,
address _pegKeeper,
bool _allowed
) external {
require(
self.pegKeeperChangeInitiated == 0,
"Peg keeper update already initiated"
);

/* solhint-disable not-rely-on-time */
self.pegKeeper = _pegKeeper;
self.allowed = _allowed;
self.pegKeeperChangeInitiated = block.timestamp;
emit PegKeeperUpdateStarted(_pegKeeper, _allowed, block.timestamp);
/* solhint-enable not-rely-on-time */
}

/// @notice Finalizes the peg keeper status update process.
/// @dev Can be called after the governance delay elapses.
function finalizePegKeeperUpdate(
PegKeeperData storage self,
uint256 governanceDelay
)
external
onlyAfterGovernanceDelay(self.pegKeeperChangeInitiated, governanceDelay)
{
emit PegKeeperUpdated(self.pegKeeper, self.allowed);

self.pegKeeper = address(0);
self.allowed = false;
self.pegKeeperChangeInitiated = 0;
}

/// @notice Cancels a pending peg keeper status update.
function cancelPegKeeperUpdate(PegKeeperData storage self) internal {
require(
self.pegKeeperChangeInitiated != 0,
"Peg keeper update not initiated"
);

emit PegKeeperUpdateCanceled(self.pegKeeper, self.allowed);

self.pegKeeper = address(0);
self.allowed = false;
self.pegKeeperChangeInitiated = 0;
}
}
24 changes: 23 additions & 1 deletion solidity/contracts/bridge/BridgeState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,19 @@ library BridgeState {
// governance wiring; changing it afterwards requires a dedicated
// upgrade path of the Bridge implementation.
address rebateStaking;
// Permanently disables one-time rebate staking wiring.
bool rebateStakingDisabled;
// DAO-designated peg keepers exempt from deposit and redemption
// treasury fees.
mapping(address => bool) pegKeepers;
// 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;
}

event DepositParametersUpdated(
Expand Down Expand Up @@ -385,6 +390,8 @@ library BridgeState {

event TreasuryUpdated(address treasury);

event PegKeeperUpdated(address pegKeeper, bool allowed);

event RedemptionWatchtowerSet(address redemptionWatchtower);

// Event emitted when the rebate staking address is initialized. Declared
Expand Down Expand Up @@ -846,6 +853,19 @@ library BridgeState {
emit TreasuryUpdated(_treasury);
}

/// @notice Updates a peg keeper status. Peg keepers are exempt from
/// deposit and redemption treasury fees.
/// @param _pegKeeper Peg keeper address.
/// @param _allowed True if the address should be allowed, false otherwise.
function updatePegKeeper(
Storage storage self,
address _pegKeeper,
bool _allowed
) internal {
self.pegKeepers[_pegKeeper] = _allowed;
emit PegKeeperUpdated(_pegKeeper, _allowed);
}

/// @notice Sets the redemption watchtower address.
/// @param _redemptionWatchtower Address of the redemption watchtower.
/// @dev Requirements:
Expand Down Expand Up @@ -882,6 +902,8 @@ library BridgeState {
function setRebateStaking(Storage storage self, address _rebateStaking)
internal
{
require(!self.rebateStakingDisabled, "Rebate staking disabled");

require(self.rebateStaking == address(0), "Rebate staking already set");

require(
Expand Down
18 changes: 11 additions & 7 deletions solidity/contracts/bridge/Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,17 @@ library Deposit {
: 0;
deposit.extraData = extraData;

if (deposit.treasuryFee > 0 && self.rebateStaking != address(0)) {
deposit.treasuryFee = RebateStaking(self.rebateStaking)
.applyForRebate(
deposit.depositor,
deposit.treasuryFee,
RebateStaking.TreasuryFeeType.Deposit
);
if (deposit.treasuryFee > 0) {
if (self.pegKeepers[deposit.depositor]) {
deposit.treasuryFee = 0;
} else if (self.rebateStaking != address(0)) {
deposit.treasuryFee = RebateStaking(self.rebateStaking)
.applyForRebate(
deposit.depositor,
deposit.treasuryFee,
RebateStaking.TreasuryFeeType.Deposit
);
}
}

_emitDepositRevealedEvent(fundingTxHash, fundingOutputAmount, reveal);
Expand Down
Loading
Loading