Skip to content
Draft
14 changes: 11 additions & 3 deletions solidity/contracts/bridge/BridgeGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,10 @@ contract BridgeGovernance is Ownable {
event TreasuryUpdateStarted(address newTreasury, uint256 timestamp);
event TreasuryUpdated(address treasury);

// Emitted by the linked BridgeGovernanceParameters library through
// delegatecall, hence its address is this BridgeGovernance contract.
// Redeclare the exact signature so governance tooling can decode it
// from the BridgeGovernance ABI.
event ReservationParametersUpdateStarted(
address newReservationVault,
uint64 newReservationMinAmount,
Expand All @@ -299,6 +303,7 @@ contract BridgeGovernance is Ownable {
uint32 newReservationGracePeriod,
uint64 newReservationMaxTotalAmount,
uint32 newMaxReservationsPerWallet,
uint32 newReservationActionTimeout,
uint256 timestamp
);

Expand Down Expand Up @@ -1834,7 +1839,8 @@ contract BridgeGovernance is Ownable {
uint32 _newReservationTermSeconds,
uint32 _newReservationGracePeriod,
uint64 _newReservationMaxTotalAmount,
uint32 _newMaxReservationsPerWallet
uint32 _newMaxReservationsPerWallet,
uint32 _newReservationActionTimeout
) external onlyOwner {
reservationData.beginReservationParametersUpdate(
_newReservationVault,
Expand All @@ -1843,7 +1849,8 @@ contract BridgeGovernance is Ownable {
_newReservationTermSeconds,
_newReservationGracePeriod,
_newReservationMaxTotalAmount,
_newMaxReservationsPerWallet
_newMaxReservationsPerWallet,
_newReservationActionTimeout
);
}

Expand All @@ -1861,7 +1868,8 @@ contract BridgeGovernance is Ownable {
staged.newReservationTermSeconds,
staged.newReservationGracePeriod,
staged.newReservationMaxTotalAmount,
staged.newMaxReservationsPerWallet
staged.newMaxReservationsPerWallet,
staged.newReservationActionTimeout
);
}
}
7 changes: 6 additions & 1 deletion solidity/contracts/bridge/BridgeGovernanceParameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,7 @@ library BridgeGovernanceParameters {
uint32 newReservationGracePeriod;
uint64 newReservationMaxTotalAmount;
uint32 newMaxReservationsPerWallet;
uint32 newReservationActionTimeout;
uint256 reservationParametersChangeInitiated;
}

Expand All @@ -1591,6 +1592,7 @@ library BridgeGovernanceParameters {
uint32 newReservationGracePeriod,
uint64 newReservationMaxTotalAmount,
uint32 newMaxReservationsPerWallet,
uint32 newReservationActionTimeout,
uint256 timestamp
);

Expand All @@ -1605,7 +1607,8 @@ library BridgeGovernanceParameters {
uint32 _newReservationTermSeconds,
uint32 _newReservationGracePeriod,
uint64 _newReservationMaxTotalAmount,
uint32 _newMaxReservationsPerWallet
uint32 _newMaxReservationsPerWallet,
uint32 _newReservationActionTimeout
) external {
/* solhint-disable not-rely-on-time */
self.newReservationVault = _newReservationVault;
Expand All @@ -1615,6 +1618,7 @@ library BridgeGovernanceParameters {
self.newReservationGracePeriod = _newReservationGracePeriod;
self.newReservationMaxTotalAmount = _newReservationMaxTotalAmount;
self.newMaxReservationsPerWallet = _newMaxReservationsPerWallet;
self.newReservationActionTimeout = _newReservationActionTimeout;
self.reservationParametersChangeInitiated = block.timestamp;
emit ReservationParametersUpdateStarted(
_newReservationVault,
Expand All @@ -1624,6 +1628,7 @@ library BridgeGovernanceParameters {
_newReservationGracePeriod,
_newReservationMaxTotalAmount,
_newMaxReservationsPerWallet,
_newReservationActionTimeout,
block.timestamp
);
/* solhint-enable not-rely-on-time */
Expand Down
37 changes: 36 additions & 1 deletion solidity/contracts/bridge/BridgeState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ import "./MovingFunds.sol";
import "../bank/Bank.sol";

library BridgeState {
/// @notice Reveal-time facts for a deposit routed to the reservation
/// vault. Both fields fit in one storage word.
struct PendingReservedDeposit {
// Wallet committed by the deposit script and therefore the only
// wallet that can be authorized to anchor the deposit.
bytes20 walletPubKeyHash;
// Exact Bitcoin refund locktime validated at reveal time. Zero is a
// sentinel used when the reveal-ahead validation was disabled.
uint32 refundDeadline;
}

struct Storage {
// Address of the Bank the Bridge belongs to.
Bank bank;
Expand Down Expand Up @@ -374,14 +385,38 @@ library BridgeState {
// the fallback delegatecall at new code is equivalent to a Bridge
// implementation change.
address reservationRouter;
// Time in seconds after which a requested reservation action
// (acceptance anchor, re-anchor, dissolution) can be reported
// timed out. Reserved redemptions use `redemptionTimeout` instead.
// Snapshotted into each action record at request time.
uint32 reservationActionTimeout;
// Collection of all reservation action generation records indexed
// by `keccak256(reservationKey | requestNonce)`. Each record
// snapshots every proof- and settlement-critical parameter of one
// requested action generation. Terminal records (TimedOut, Vetoed,
// Superseded, Settled) are never deleted: timed-out generations
// must keep accepting late proofs of their confirmed Bitcoin
// transactions.
mapping(uint256 => Reservation.ReservationAction) reservationActions;
// Per-wallet main-UTXO action lock: maps the 20-byte wallet public
// key hash to the reservation key of the wallet's in-flight
// dissolution, or zero when none is pending. At most one
// dissolution per wallet may be in flight — concurrent
// dissolutions of a no-main-UTXO wallet could all confirm on
// Bitcoin with only the first being provable.
mapping(bytes20 => uint256) walletPendingDissolution;
// Reveal-time facts for deposits routed to the reservation vault.
// The exact refund deadline is immutable even when governance later
// changes `depositRevealAheadPeriod`.
mapping(uint256 => PendingReservedDeposit) pendingReservedDeposit;
// 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[42] __gap;
uint256[39] __gap;
}

event DepositParametersUpdated(
Expand Down
33 changes: 24 additions & 9 deletions solidity/contracts/bridge/Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,12 @@ library Deposit {
"Vault is not trusted"
);

uint32 refundDeadline;
if (self.depositRevealAheadPeriod > 0) {
validateDepositRefundLocktime(self, reveal.refundLocktime);
refundDeadline = validateDepositRefundLocktime(
self,
reveal.refundLocktime
);
}

bytes memory expectedScript;
Expand Down Expand Up @@ -317,13 +321,12 @@ library Deposit {
)
.hash256View();

DepositRequest storage deposit = self.deposits[
uint256(
keccak256(
abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)
)
uint256 depositKey = uint256(
keccak256(
abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)
)
];
);
DepositRequest storage deposit = self.deposits[depositKey];
require(deposit.revealedAt == 0, "Deposit already revealed");

uint64 fundingOutputAmount = fundingOutput.extractValue();
Expand Down Expand Up @@ -352,6 +355,16 @@ library Deposit {
);
}

if (
reveal.vault != address(0) && reveal.vault == self.reservationVault
) {
self.pendingReservedDeposit[depositKey] = BridgeState
.PendingReservedDeposit(
reveal.walletPubKeyHash,
refundDeadline
);
}

_emitDepositRevealedEvent(fundingTxHash, fundingOutputAmount, reveal);
}

Expand Down Expand Up @@ -426,10 +439,10 @@ library Deposit {
function validateDepositRefundLocktime(
BridgeState.Storage storage self,
bytes4 refundLocktime
) internal view {
) internal view returns (uint32 depositRefundableTimestamp) {
// Convert the refund locktime byte array to a LE integer. This is
// the moment in time when the deposit become refundable.
uint32 depositRefundableTimestamp = BTCUtils.reverseUint32(
depositRefundableTimestamp = BTCUtils.reverseUint32(
uint32(refundLocktime)
);
// According to https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
Expand All @@ -450,5 +463,7 @@ library Deposit {
depositRefundableTimestamp,
"Deposit refund locktime is too close"
);

return depositRefundableTimestamp;
}
}
1 change: 1 addition & 0 deletions solidity/contracts/bridge/DepositSweep.sol
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ library DepositSweep {
wallet.mainUtxoHash = keccak256(
abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
);
Wallets.rearmMovingFundsTimeout(self, walletPubKeyHash);

// slither-disable-next-line reentrancy-events
emit DepositsSwept(walletPubKeyHash, sweepTxHash);
Expand Down
42 changes: 38 additions & 4 deletions solidity/contracts/bridge/IReservationBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,44 @@ import "./Reservation.sol";
/// `delegatecall`, so callers use this interface against the Bridge
/// address itself — the Bridge contract type does not declare them.
interface IReservationBridge {
/// @notice See `ReservationRouter.requestReservationAcceptance`.
function requestReservationAcceptance(
uint256 reservationKey,
bytes20 walletPubKeyHash
) external;

/// @notice See `ReservationRouter.requestReservedRedemption`.
function requestReservedRedemption(
uint256 reservationKey,
address redeemer,
bytes calldata redeemerOutputScript
bytes calldata redeemerOutputScript,
bool feePaid,
bool useRetryCredit
) external;

/// @notice See `ReservationRouter.requestReservationReanchor`.
function requestReservationReanchor(
uint256 reservationKey,
bytes20 targetWalletPubKeyHash
) external;

/// @notice See `ReservationRouter.requestReservationDissolution`.
function requestReservationDissolution(uint256 reservationKey) external;

/// @notice See `ReservationRouter.notifyReservationActionTimeout`.
function notifyReservationActionTimeout(
uint256 reservationKey,
uint32[] calldata walletMembersIDs
) external;

/// @notice See `ReservationRouter.extendReservation`.
function extendReservation(uint256 reservationKey) external;

/// @notice See `ReservationRouter.notifyReservedRedemptionVeto`.
function notifyReservedRedemptionVeto(uint256 reservationKey) external;
function notifyReservedRedemptionVeto(
uint256 reservationKey,
uint64 requestNonce
) external;

/// @notice See `ReservationRouter.updateReservationParameters`.
function updateReservationParameters(
Expand All @@ -44,7 +70,8 @@ interface IReservationBridge {
uint32 reservationTermSeconds,
uint32 reservationGracePeriod,
uint64 reservationMaxTotalAmount,
uint32 maxReservationsPerWallet
uint32 maxReservationsPerWallet,
uint32 reservationActionTimeout
) external;

/// @notice Bridge treasury address. Declared by the Bridge contract
Expand All @@ -59,6 +86,12 @@ interface IReservationBridge {
view
returns (Reservation.ReservationRequest memory);

/// @notice See `ReservationRouter.reservationActions`.
function reservationActions(uint256 reservationKey, uint64 requestNonce)
external
view
returns (Reservation.ReservationAction memory);

/// @notice See `ReservationRouter.reservationParameters`.
function reservationParameters()
external
Expand All @@ -71,6 +104,7 @@ interface IReservationBridge {
uint32 reservationGracePeriod,
uint64 reservationMaxTotalAmount,
uint64 reservationTotalAmount,
uint32 maxReservationsPerWallet
uint32 maxReservationsPerWallet,
uint32 reservationActionTimeout
);
}
Loading
Loading