Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
b3d69f6
implement global lock
julianmrodri Jun 25, 2025
e033781
add docs
julianmrodri Jun 25, 2025
fefda24
implement global lock in functions
julianmrodri Jun 25, 2025
c49347e
fix lint
julianmrodri Jun 25, 2025
2805843
fix docs
julianmrodri Jun 25, 2025
95306c0
fix lint
julianmrodri Jun 25, 2025
311e0c1
update version
julianmrodri Jun 25, 2025
3b33b68
disabled 4.0 explicit test
julianmrodri Jun 25, 2025
235bc5c
tests and nit
julianmrodri Jun 27, 2025
c24cd4e
additional tests
julianmrodri Jun 27, 2025
c58fa2a
additional tests
julianmrodri Jun 27, 2025
00b25bb
claim rewards test
julianmrodri Jun 27, 2025
bba71b1
fix order
julianmrodri Jun 27, 2025
e190f23
fix style
julianmrodri Jun 27, 2025
a1aba68
remove comments
julianmrodri Jun 27, 2025
5ee0c8d
upg tests
julianmrodri Jul 1, 2025
24eaf0a
fix docs
julianmrodri Jul 3, 2025
bc3dbea
add trusted filler support
julianmrodri Aug 26, 2025
6db68d9
upgrade tests updated
julianmrodri Aug 26, 2025
99725b3
update version
julianmrodri Aug 26, 2025
48cd614
fix version
julianmrodri Aug 26, 2025
f5dd036
fix upgrade tests
julianmrodri Aug 26, 2025
8c7826e
fix storage slot
julianmrodri Aug 27, 2025
94b2965
replace asserts
julianmrodri Aug 28, 2025
d91cb0a
fix lint
julianmrodri Aug 28, 2025
35b5cd1
additional require tests
julianmrodri Aug 28, 2025
32b95c2
bump trusted fillers package
julianmrodri Aug 28, 2025
b8b1a18
apply changes to Dutch Trade
julianmrodri Aug 28, 2025
92de659
fix balance check
julianmrodri Aug 28, 2025
74a6182
set partially fillable
julianmrodri Aug 28, 2025
89ca897
set partially fillable
julianmrodri Aug 28, 2025
d7490a5
final changes
julianmrodri Aug 29, 2025
f1a0675
Minor changes + dedupe
akshatmittal Aug 29, 2025
ee78eb1
fixes in tests
julianmrodri Sep 1, 2025
203b29d
fix revenue test
julianmrodri Sep 1, 2025
7710267
add tests and fix can settle
julianmrodri Sep 1, 2025
1ade3cc
rollback
julianmrodri Sep 1, 2025
ad45a90
add isComponent check
julianmrodri Sep 3, 2025
eaf7bae
fix lint
julianmrodri Sep 3, 2025
40e1269
implement as modifier
julianmrodri Sep 3, 2025
6393f71
Merge pull request #1255 from reserve-protocol/component-check
julianmrodri Sep 3, 2025
464a6f0
Merge branch '4.1.0' into 4.2.0
tbrent Sep 3, 2025
aa69bae
BackingManager under limit
tbrent Sep 3, 2025
85f83d6
custom errors for StRSR
tbrent Sep 3, 2025
470c0f2
tests
tbrent Sep 3, 2025
3f4588a
Merge branch '4.2.0' of github.com:reserve-protocol/protocol into 4.2.0
tbrent Sep 3, 2025
f7c915c
P0 + extreme tests
tbrent Sep 3, 2025
95572f7
missing errors
tbrent Sep 3, 2025
a39811b
fixes from audit
julianmrodri Sep 10, 2025
5ce52b8
additional testing and dependencies
julianmrodri Sep 11, 2025
8f61bb1
fix report violation
julianmrodri Sep 23, 2025
4f17150
make test flexible
julianmrodri Sep 24, 2025
9985a82
can settle edge case
julianmrodri Sep 24, 2025
d5d6232
Merge pull request #1257 from reserve-protocol/4.2.0-fixes
julianmrodri Sep 25, 2025
a536730
Merge branch '4.0.0' into 4.2.0
tbrent Sep 26, 2025
828bd87
yarn.lock
tbrent Sep 26, 2025
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

# 4.2.0

- **Trusted Fillers**: Rtokens are now integrated with [Trusted Fillers](https://github.com/reserve-protocol/trusted-fillers/) and can be enabled by governance to allow async fillers to compete in auctions to provide better prices. All auction limitations still apply to these fillers. Currently, the only supported async filler is CoW Swap.

# 4.1.0

This release implements a global lock on `Main` (by inherinting from `GlobalReentrancyGuard.sol`), which can be used by individual components to define the `globalNonReentrant` modifier and allow global reentrancy checks accross core protocol functions. See [docs/solidity-style.md](./docs/solidity-style.md#Reentrancy-safety)

- Adds `mixins/GlobalReentrancyGuard.sol` contract
- Implements the `globalNonReentrant` modifier on `ComponentP1`
- Adds to `globalNonReentrant` modifier on impacted functions to enforce global reentrancy checks

# 4.0.0

This release prepares the core protocol for veRSR through the introduction of 3 registries (`DAOFeeRegistry`, `AssetPluginRegistry`, and `VersionRegistry`) and through restricting component upgrades to be handled by `Main`, where upgrade constraints can be enforced.
Expand Down
4 changes: 4 additions & 0 deletions common/registries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ interface IRegistries {
versionRegistry: string
assetPluginRegistry: string
daoFeeRegistry: string
trustedFillerRegistry: string
}

interface IRegistryControl {
Expand All @@ -26,6 +27,7 @@ export const registryConfig: Record<string, RegistryChainRecord> = {
versionRegistry: '',
assetPluginRegistry: '',
daoFeeRegistry: '',
trustedFillerRegistry: '0x279ccF56441fC74f1aAC39E7faC165Dec5A88B3A',
},
},
'8453': {
Expand All @@ -38,6 +40,7 @@ export const registryConfig: Record<string, RegistryChainRecord> = {
versionRegistry: '',
assetPluginRegistry: '',
daoFeeRegistry: '',
trustedFillerRegistry: '0x72DB5f49D0599C314E2f2FEDf6Fe33E1bA6C7A18',
},
},
'56': {
Expand All @@ -50,6 +53,7 @@ export const registryConfig: Record<string, RegistryChainRecord> = {
versionRegistry: '',
assetPluginRegistry: '',
daoFeeRegistry: '',
trustedFillerRegistry: '0x08424d7C52bf9edd4070701591Ea3FE6dca6449B',
},
},
}
Expand Down
14 changes: 14 additions & 0 deletions contracts/interfaces/IBroker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@reserve-protocol/trusted-fillers/contracts/interfaces/ITrustedFillerRegistry.sol";
import "./IAsset.sol";
import "./IComponent.sol";
import "./IGnosis.sol";
Expand Down Expand Up @@ -40,6 +41,7 @@ interface IBroker is IComponent {
event DutchAuctionLengthSet(uint48 oldVal, uint48 newVal);
event BatchTradeDisabledSet(bool prevVal, bool newVal);
event DutchTradeDisabledSet(IERC20Metadata indexed erc20, bool prevVal, bool newVal);
event TrustedFillerRegistrySet(address trustedFillerRegistry, bool isEnabled);

// Initialization
function init(
Expand Down Expand Up @@ -71,6 +73,12 @@ interface IExtendedBroker is IBroker {
function setBatchTradeImplementation(ITrade newTradeImplementation) external;

function setDutchTradeImplementation(ITrade newTradeImplementation) external;

function setTrustedFillerRegistry(address newRegistry, bool enabled) external;

function trustedFillerRegistry() external view returns (ITrustedFillerRegistry);

function trustedFillerEnabled() external view returns (bool);
}

interface TestIBroker is IBroker {
Expand Down Expand Up @@ -98,4 +106,10 @@ interface TestIBroker is IBroker {

// only present on pre-3.0.0 Brokers; used by EasyAuction regression test
function disabled() external view returns (bool);

function setTrustedFillerRegistry(address newRegistry, bool enabled) external;

function trustedFillerRegistry() external view returns (ITrustedFillerRegistry);

function trustedFillerEnabled() external view returns (bool);
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./IVersioned.sol";
import "../registry/VersionRegistry.sol";
import "../registry/AssetPluginRegistry.sol";
import "../registry/DAOFeeRegistry.sol";
import "@reserve-protocol/trusted-fillers/contracts/interfaces/ITrustedFillerRegistry.sol";

/**
* @title DeploymentParams
Expand Down Expand Up @@ -100,6 +101,7 @@ interface IDeployer is IVersioned {
VersionRegistry versionRegistry;
AssetPluginRegistry assetPluginRegistry;
DAOFeeRegistry daoFeeRegistry;
ITrustedFillerRegistry trustedFillerRegistry;
}

/// Deploys an instance of the entire system
Expand Down
8 changes: 8 additions & 0 deletions contracts/interfaces/IMain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,17 @@ interface IMain is IVersioned, IAuth, IComponentRegistry {
function versionRegistry() external view returns (VersionRegistry);

function daoFeeRegistry() external view returns (DAOFeeRegistry);

// === Control flow ===

function beginTx() external;

function endTx() external;
}

interface TestIMain is IMain {
error ReentrancyGuardReentrantCall();

function setVersionRegistry(VersionRegistry) external;

function setAssetPluginRegistry(AssetPluginRegistry) external;
Expand Down
2 changes: 1 addition & 1 deletion contracts/mixins/Versioned.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.19;
import "../interfaces/IVersioned.sol";

// This value should be updated on each release
string constant VERSION = "4.0.0";
string constant VERSION = "4.2.0";

/**
* @title Versioned
Expand Down
10 changes: 10 additions & 0 deletions contracts/mocks/TrustedFillersImport.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

// This file exists solely to force Hardhat to compile the trusted-fillers contracts
// These imports ensure the contracts are available for testing

import "@reserve-protocol/trusted-fillers/contracts/TrustedFillerRegistry.sol";
// Note: CowSwapFiller cannot be imported due to OpenZeppelin version conflict
// CowSwapFiller uses OZ 5.1.0 (Math.Rounding.Up) while protocol uses OZ 4.9.6 (Math.Rounding.Ceil)
// import "@reserve-protocol/trusted-fillers/contracts/fillers/cowswap/CowSwapFiller.sol";
8 changes: 8 additions & 0 deletions contracts/p0/Main.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain {
function daoFeeRegistry() external pure returns (DAOFeeRegistry) {
return DAOFeeRegistry(address(0));
}

// === Control flow ===

// solhint-disable-next-line no-empty-blocks
function beginTx() external virtual {}

// solhint-disable-next-line no-empty-blocks
function endTx() external virtual {}
}
9 changes: 9 additions & 0 deletions contracts/p0/mixins/Component.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,13 @@ abstract contract ComponentP0 is Versioned, Initializable, ContextUpgradeable, I
require(main.hasRole(OWNER, _msgSender()), "governance only");
_;
}

// === Control Flow ===
// In P0 we do not apply locks

modifier globalNonReentrant() {
main.beginTx();
_;
main.endTx();
}
}
10 changes: 3 additions & 7 deletions contracts/p1/BackingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager {
// checks: erc20 in assetRegistry
// action: set allowance on erc20 for rToken to UINT_MAX
// Using two safeApprove calls instead of safeIncreaseAllowance to support USDT
function grantRTokenAllowance(IERC20 erc20) external notFrozen {
function grantRTokenAllowance(IERC20 erc20) external notFrozen globalNonReentrant {
require(assetRegistry.isRegistered(erc20), "erc20 unregistered");
// == Interaction ==
IERC20(address(erc20)).safeApprove(address(rToken), 0);
Expand Down Expand Up @@ -105,9 +105,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager {
/// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable
/// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION
/// @custom:interaction not RCEI; nonReentrant
// untested:
// OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high
function rebalance(TradeKind kind) external nonReentrant {
function rebalance(TradeKind kind) external globalNonReentrant {
requireNotTradingPausedOrFrozen();

// == Refresh ==
Expand Down Expand Up @@ -177,9 +175,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager {
/// Forward revenue to RevenueTraders; reverts if not fully collateralized
/// @param erc20s The tokens to forward
/// @custom:interaction not RCEI; nonReentrant
// untested:
// OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high
function forwardRevenue(IERC20[] calldata erc20s) external nonReentrant {
function forwardRevenue(IERC20[] calldata erc20s) external globalNonReentrant {
requireNotTradingPausedOrFrozen();
require(ArrayLib.allUnique(erc20s), "duplicate tokens");

Expand Down
34 changes: 33 additions & 1 deletion contracts/p1/Broker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@reserve-protocol/trusted-fillers/contracts/interfaces/ITrustedFillerRegistry.sol";
import "../interfaces/IBroker.sol";
import "../interfaces/IMain.sol";
import "../interfaces/ITrade.sol";
Expand Down Expand Up @@ -37,7 +38,7 @@

/// @custom:oz-renamed-from gnosis
// Deprecated in 4.0.0
IGnosis public gnosis_DEPRECATED;

Check warning on line 41 in contracts/p1/Broker.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Variable name must be in mixedCase

/// @custom:oz-renamed-from auctionLength
// {s} the length of a Gnosis EasyAuction. Governance parameter.
Expand Down Expand Up @@ -67,6 +68,11 @@

IRToken private rToken;

// === 4.2.0 ===

ITrustedFillerRegistry public trustedFillerRegistry;
bool public trustedFillerEnabled;

// ==== Invariant ====
// (trades[addr] == true) iff this contract has created an ITrade clone at addr

Expand Down Expand Up @@ -172,6 +178,16 @@

// === Setters ===

/// @dev _newFillerRegistry must be the already set registry if already set. This is to ensure
/// correctness and in order to be explicit what registry is being enabled/disabled.
/// @custom:governance
function setTrustedFillerRegistry(address _newFillerRegistry, bool _enabled)
external
governance
{
_setTrustedFillerRegistry(_newFillerRegistry, _enabled);
}

/// @custom:main
function setBatchTradeImplementation(ITrade newTradeImplementation) public onlyMain {
require(
Expand Down Expand Up @@ -289,10 +305,26 @@
return asset.lastSave() == block.timestamp || address(asset.erc20()) == address(rToken);
}

function _setTrustedFillerRegistry(address _newFillerRegistry, bool _enabled) internal {
if (address(trustedFillerRegistry) != _newFillerRegistry) {
require(
address(trustedFillerRegistry) == address(0),
"trusted filler registry already set"
);
trustedFillerRegistry = ITrustedFillerRegistry(_newFillerRegistry);
}

if (trustedFillerEnabled != _enabled) {
trustedFillerEnabled = _enabled;
}

emit TrustedFillerRegistrySet(address(trustedFillerRegistry), trustedFillerEnabled);
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[41] private __gap;
Comment thread
julianmrodri marked this conversation as resolved.
uint256[40] private __gap;
}
9 changes: 9 additions & 0 deletions contracts/p1/Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ contract DeployerP1 is IDeployer, Versioned {
// Init Furnace
components.furnace.init(main, params.rewardRatio);

// Init Broker
components.broker.init(
main,
_implementations.trading.gnosisTrade,
Expand All @@ -218,6 +219,14 @@ contract DeployerP1 is IDeployer, Versioned {
params.dutchAuctionLength
);

// Assign TrustedFillerRegistry
if (address(registries.trustedFillerRegistry) != address(0)) {
IExtendedBroker(address(components.broker)).setTrustedFillerRegistry(
address(registries.trustedFillerRegistry),
true
);
}

// Init StRSR
{
string memory stRSRSymbol = string(abi.encodePacked(StringLib.toLower(symbol), "RSR"));
Expand Down
22 changes: 21 additions & 1 deletion contracts/p1/Main.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/IMain.sol";
import "./mixins/GlobalReentrancyGuard.sol";
import "../mixins/ComponentRegistry.sol";
import "../mixins/Auth.sol";
import "../mixins/Versioned.sol";
Expand All @@ -19,7 +20,15 @@
* @notice The center of the system around which Components orbit.
*/
// solhint-disable max-states-count
contract MainP1 is Versioned, Initializable, Auth, ComponentRegistry, UUPSUpgradeable, IMain {
contract MainP1 is
Versioned,
Initializable,
Auth,
ComponentRegistry,
UUPSUpgradeable,
GlobalReentrancyGuard,
IMain
{
IERC20 public rsr;
VersionRegistry public versionRegistry;
AssetPluginRegistry public assetPluginRegistry;
Expand All @@ -39,6 +48,7 @@
require(address(rsr_) != address(0), "invalid RSR address");
__Auth_init(shortFreeze_, longFreeze_);
__ComponentRegistry_init(components);
__ReentrancyGuard_init();
__UUPSUpgradeable_init();

rsr = rsr_;
Expand Down Expand Up @@ -149,13 +159,23 @@
);
}

// === Control Flow ===

function beginTx() external virtual {
_nonReentrantBefore();
}

function endTx() external virtual {
_nonReentrantAfter();
}

// === Upgradeability ===
function _authorizeUpgrade(address) internal view override {
require(msg.sender == address(this), "not self");
}

function _upgradeProxy(address proxy, address implementation) internal {
(bool success, ) = proxy.call(

Check warning on line 178 in contracts/p1/Main.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Avoid to use low level calls
abi.encodeWithSelector(UUPSUpgradeable.upgradeTo.selector, implementation)
);

Expand Down
12 changes: 8 additions & 4 deletions contracts/p1/RToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
/// @param amount {qRTok} The quantity of RToken to issue
/// @custom:interaction RCEI
// BU exchange rate cannot decrease, and it can only increase when < FIX_ONE.
function issueTo(address recipient, uint256 amount) public notIssuancePausedOrFrozen {
function issueTo(address recipient, uint256 amount)
public
notIssuancePausedOrFrozen
globalNonReentrant
{
require(amount != 0, "Cannot issue zero");

// == Refresh ==
Expand Down Expand Up @@ -180,7 +184,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
/// @param recipient The address to receive the backing collateral tokens
/// @param amount {qRTok} The quantity {qRToken} of RToken to redeem
/// @custom:interaction RCEI
function redeemTo(address recipient, uint256 amount) public notFrozen {
function redeemTo(address recipient, uint256 amount) public notFrozen globalNonReentrant {
// == Refresh ==
assetRegistry.refresh();

Expand Down Expand Up @@ -258,7 +262,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
uint192[] memory portions,
address[] memory expectedERC20sOut,
uint256[] memory minAmounts
) external notFrozen {
) external notFrozen globalNonReentrant {
// == Refresh ==
assetRegistry.refresh();

Expand Down Expand Up @@ -427,7 +431,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {

/// Sends all token balance of erc20 (if it is registered) to the BackingManager
/// @custom:interaction
function monetizeDonations(IERC20 erc20) external notTradingPausedOrFrozen {
function monetizeDonations(IERC20 erc20) external notTradingPausedOrFrozen globalNonReentrant {
require(assetRegistry.isRegistered(erc20), "erc20 unregistered");
IERC20Upgradeable(address(erc20)).safeTransfer(
address(backingManager),
Expand Down
Loading
Loading