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

Require redemption throttle limits above issuance throttle limits #1209

Merged
merged 33 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f75de7a
add redemption throttle check.
pmckelvy1 Sep 18, 2024
1b6a816
fixing tests.
pmckelvy1 Sep 19, 2024
bd1dc5f
fixing tests.
pmckelvy1 Sep 19, 2024
5d0445e
fixing tests.
pmckelvy1 Sep 19, 2024
d8198f9
fixing tests.
pmckelvy1 Sep 19, 2024
4304589
fixing tests.
pmckelvy1 Sep 19, 2024
0514db9
fixing tests.
pmckelvy1 Sep 19, 2024
fee09c6
fix linting.
pmckelvy1 Sep 19, 2024
d693380
fix fixture for integration tests.
pmckelvy1 Sep 19, 2024
13675b7
fixing fixtures.
pmckelvy1 Sep 19, 2024
c38a282
fix curve test fixtures.
pmckelvy1 Sep 19, 2024
d207b3d
fixing facade tests.
pmckelvy1 Sep 20, 2024
bcc4da0
fix broker extremes, fix atoken ctoken tests.
pmckelvy1 Sep 20, 2024
0820cd8
fix trading extremes.
pmckelvy1 Sep 20, 2024
01374d7
fix p0.
pmckelvy1 Sep 20, 2024
c2c418f
fix linting.
pmckelvy1 Sep 20, 2024
bae9e0c
fix furnace extreme test.
pmckelvy1 Sep 20, 2024
f8fc4f7
more accurate reflection of current extreme tests.
pmckelvy1 Sep 20, 2024
0c08710
fix facade test.
pmckelvy1 Sep 20, 2024
c62701c
fix trading extremes.
pmckelvy1 Sep 20, 2024
c3fe2b5
no mod, not sure whats wrong
pmckelvy1 Sep 23, 2024
70b7893
remove only.
pmckelvy1 Sep 23, 2024
e04cb43
address comments.
pmckelvy1 Sep 24, 2024
b5618ea
fix conflicts.
pmckelvy1 Sep 25, 2024
6afb7ac
Merge branch '4.0.0' into red-throttle-limits
pmckelvy1 Sep 25, 2024
8f8eee2
fix extremes?
pmckelvy1 Sep 25, 2024
08da04f
fix rtoken extreme tests.
pmckelvy1 Sep 25, 2024
91b4370
gt to lt...why wasnt it failing before?
pmckelvy1 Sep 26, 2024
9faf99b
use issuanceAvailable.
pmckelvy1 Sep 26, 2024
3376a76
return early if bad throttle combo.
pmckelvy1 Sep 26, 2024
5e8cae8
owner.
pmckelvy1 Sep 30, 2024
6182ce4
fix sub(1)."
pmckelvy1 Sep 30, 2024
4448db3
use uint192
tbrent Oct 29, 2024
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
5 changes: 5 additions & 0 deletions contracts/interfaces/IRToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ interface TestIRToken is IRToken {

function setRedemptionThrottleParams(ThrottleLib.Params calldata) external;

function setThrottleParams(
ThrottleLib.Params calldata issuanceParams,
ThrottleLib.Params calldata redemptionParams
) external;

function issuanceThrottleParams() external view returns (ThrottleLib.Params memory);

function redemptionThrottleParams() external view returns (ThrottleLib.Params memory);
Expand Down
58 changes: 55 additions & 3 deletions contracts/p0/RToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken {
uint192 public constant MAX_THROTTLE_PCT_AMT = 1e18; // {qRTok}
uint192 public constant MIN_EXCHANGE_RATE = 1e9; // D18{BU/rTok}
uint192 public constant MAX_EXCHANGE_RATE = 1e27; // D18{BU/rTok}
uint256 public constant MIN_THROTTLE_DELTA = 25; // {%}

/// Weakly immutable: expected to be an IPFS link but could be the mandate itself
string public mandate;
Expand All @@ -54,8 +55,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken {
__ERC20Permit_init(name_);

mandate = mandate_;
setIssuanceThrottleParams(issuanceThrottleParams_);
setRedemptionThrottleParams(redemptionThrottleParams_);
setIssuanceThrottleParams(issuanceThrottleParams_);

issuanceThrottle.lastTimestamp = uint48(block.timestamp);
redemptionThrottle.lastTimestamp = uint48(block.timestamp);
Expand Down Expand Up @@ -330,25 +331,76 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken {

/// @custom:governance
function setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
_setIssuanceThrottleParams(params);
require(
isRedemptionThrottleGreaterByDelta(params, redemptionThrottle.params),
"redemption throttle too low"
);
}

/// @custom:governance
function setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
_setRedemptionThrottleParams(params);
require(
isRedemptionThrottleGreaterByDelta(issuanceThrottle.params, params),
"redemption throttle too low"
);
}

function setThrottleParams(
ThrottleLib.Params calldata issuanceParams,
ThrottleLib.Params calldata redemptionParams
) external governance {
_setIssuanceThrottleParams(issuanceParams);
_setRedemptionThrottleParams(redemptionParams);
require(
isRedemptionThrottleGreaterByDelta(issuanceParams, redemptionParams),
"redemption throttle too low"
);
}

// === Private ===

function _setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small");
require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big");
require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big");
issuanceThrottle.useAvailable(totalSupply(), 0);

emit IssuanceThrottleSet(issuanceThrottle.params, params);
issuanceThrottle.params = params;
}

/// @custom:governance
function setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
function _setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small");
require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big");
require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big");
redemptionThrottle.useAvailable(totalSupply(), 0);

emit RedemptionThrottleSet(redemptionThrottle.params, params);
redemptionThrottle.params = params;
}

// === Private ===
/// @notice Checks if the redemption throttle is greater than the issuance throttle by the
/// required delta
/// @dev Compares both amtRate and pctRate individually to ensure each meets the minimum
/// delta requirement
/// @param issuance The issuance throttle parameters to compare against
/// @param redemption The redemption throttle parameters to check
/// @return bool True if redemption throttle is greater by at least MIN_THROTTLE_DELTA,
/// false otherwise
function isRedemptionThrottleGreaterByDelta(
ThrottleLib.Params memory issuance,
ThrottleLib.Params memory redemption
) private pure returns (bool) {
uint256 requiredAmtRate = issuance.amtRate +
((issuance.amtRate * MIN_THROTTLE_DELTA) / 100);
uint256 requiredPctRate = issuance.pctRate +
((issuance.pctRate * MIN_THROTTLE_DELTA) / 100);

return redemption.amtRate >= requiredAmtRate && redemption.pctRate >= requiredPctRate;
}

/// Mint an amount of RToken equivalent to amtBaskets and scale basketsNeeded up
/// @param recipient The address to receive the RTokens
Expand Down
59 changes: 55 additions & 4 deletions contracts/p1/RToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
uint192 public constant MAX_THROTTLE_PCT_AMT = 1e18; // {qRTok}
uint192 public constant MIN_EXCHANGE_RATE = 1e9; // D18{BU/rTok}
uint192 public constant MAX_EXCHANGE_RATE = 1e27; // D18{BU/rTok}
uint256 public constant MIN_THROTTLE_DELTA = 25; // {%}

/// The mandate describes what goals its governors should try to achieve. By succinctly
/// explaining the RTokens purpose and what the RToken is intended to do, it provides common
/// explaining the RToken's purpose and what the RToken is intended to do, it provides common
/// ground for the governors to decide upon priorities and how to weigh tradeoffs.
///
/// Example Mandates:
Expand Down Expand Up @@ -79,8 +80,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
furnace = main_.furnace();

mandate = mandate_;
setIssuanceThrottleParams(issuanceThrottleParams_);
setRedemptionThrottleParams(redemptionThrottleParams_);
setIssuanceThrottleParams(issuanceThrottleParams_);
tbrent marked this conversation as resolved.
Show resolved Hide resolved

issuanceThrottle.lastTimestamp = uint48(block.timestamp);
redemptionThrottle.lastTimestamp = uint48(block.timestamp);
Expand Down Expand Up @@ -443,6 +444,38 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {

/// @custom:governance
function setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
_setIssuanceThrottleParams(params);
require(
isRedemptionThrottleGreaterByDelta(params, redemptionThrottle.params),
"redemption throttle too low"
);
}

/// @custom:governance
function setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
_setRedemptionThrottleParams(params);
require(
isRedemptionThrottleGreaterByDelta(issuanceThrottle.params, params),
"redemption throttle too low"
);
}

/// @custom:governance
function setThrottleParams(
ThrottleLib.Params calldata issuanceParams,
ThrottleLib.Params calldata redemptionParams
) external governance {
_setIssuanceThrottleParams(issuanceParams);
_setRedemptionThrottleParams(redemptionParams);
require(
isRedemptionThrottleGreaterByDelta(issuanceParams, redemptionParams),
"redemption throttle too low"
);
}

// === Private Helpers ===

function _setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
tbrent marked this conversation as resolved.
Show resolved Hide resolved
require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small");
require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big");
require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big");
Expand All @@ -453,7 +486,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
}

/// @custom:governance
function setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
function _setRedemptionThrottleParams(ThrottleLib.Params calldata params) public governance {
require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small");
require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big");
require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big");
Expand All @@ -463,7 +496,25 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken {
redemptionThrottle.params = params;
}

// ==== Private ====
/// @notice Checks if the redemption throttle is greater than the issuance throttle by the
/// required delta
/// @dev Compares both amtRate and pctRate individually to ensure each meets the minimum
/// delta requirement
/// @param issuance The issuance throttle parameters to compare against
/// @param redemption The redemption throttle parameters to check
/// @return bool True if redemption throttle is greater by at least MIN_THROTTLE_DELTA,
/// false otherwise
function isRedemptionThrottleGreaterByDelta(
ThrottleLib.Params memory issuance,
ThrottleLib.Params memory redemption
) private pure returns (bool) {
uint256 requiredAmtRate = issuance.amtRate +
((issuance.amtRate * MIN_THROTTLE_DELTA) / 100);
uint256 requiredPctRate = issuance.pctRate +
((issuance.pctRate * MIN_THROTTLE_DELTA) / 100);

return redemption.amtRate >= requiredAmtRate && redemption.pctRate >= requiredPctRate;
}

/// Mint an amount of RToken equivalent to amtBaskets and scale basketsNeeded up
/// @param recipient The address to receive the RTokens
Expand Down
7 changes: 4 additions & 3 deletions test/Broker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1425,10 +1425,11 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => {
const MAX_ERC20_SUPPLY = bn('1e48') // from docs/solidity-style.md

// Max out throttles
const issuanceThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 }
const issuanceThrottleParams = { amtRate: MAX_ERC20_SUPPLY.mul(80).div(100), pctRate: 0 }
const redemptionThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 }
await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams)
await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams)
await rToken
.connect(owner)
.setThrottleParams(issuanceThrottleParams, redemptionThrottleParams)
await advanceTime(3600)

// Mint coll tokens to addr1
Expand Down
34 changes: 15 additions & 19 deletions test/Facade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1329,10 +1329,6 @@ describe('Facade + FacadeMonitor contracts', () => {
it('should return redemption available', async () => {
const issueAmount = bn('100000e18')

// Decrease redemption allowed amount
const redeemThrottleParams = { amtRate: issueAmount.div(2), pctRate: fp('0.1') } // 50K
await rToken.connect(owner).setRedemptionThrottleParams(redeemThrottleParams)

// Check with no supply
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1'))
expect(await rToken.redemptionAvailable()).to.equal(bn(0))
Expand All @@ -1341,8 +1337,13 @@ describe('Facade + FacadeMonitor contracts', () => {
// Issue some RTokens
await rToken.connect(addr1).issue(issueAmount)

// check throttles - redemption still fully available
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('0.9'))
// Decrease redemption allowed amount
const issuanceThrottleParams = { amtRate: issueAmount.div(4), pctRate: fp('0.05') } // 25K
const redeemThrottleParams = { amtRate: issueAmount.div(2), pctRate: fp('0.1') } // 50K
await rToken.connect(owner).setThrottleParams(issuanceThrottleParams, redeemThrottleParams)

// check throttles - issuance & redemption still fully available (because lower)
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1'))
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1'))

// Redeem RTokens (50% of throttle)
Expand Down Expand Up @@ -1406,7 +1407,10 @@ describe('Facade + FacadeMonitor contracts', () => {

// Set issuance throttle to percent only
const issuanceThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } // 10%
await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams)
const redemptionThrottleParams = { amtRate: fp('2'), pctRate: fp('0.2') } // 10%
await rToken
.connect(owner)
.setThrottleParams(issuanceThrottleParams, redemptionThrottleParams)

// Advance time significantly
await advanceTime(1000000000)
Expand All @@ -1417,7 +1421,7 @@ describe('Facade + FacadeMonitor contracts', () => {
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1'))

// Check redemption throttle unchanged
expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate)
expect(await rToken.redemptionAvailable()).to.equal(supplyThrottle.mul(2))
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1'))

// Issuance #3 - Should be allowed, does not exceed supply restriction
Expand All @@ -1427,26 +1431,22 @@ describe('Facade + FacadeMonitor contracts', () => {

// Check issuance throttle updated - Previous issuances recharged
expect(await rToken.issuanceAvailable()).to.equal(supplyThrottle.sub(issueAmount3))

// Hourly Limit: 210K (10% of total supply of 2.1 M)
// Available: 100 K / 201K (~ 0.47619)
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo(
fp('0.476'),
fp('0.001')
)

// Check redemption throttle unchanged
expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate)
// expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate)
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1'))

// Check all issuances are confirmed
expect(await rToken.balanceOf(addr1.address)).to.equal(
issueAmount1.add(issueAmount2).add(issueAmount3)
)

// Advance time, issuance will recharge a bit
await advanceTime(100)

// Now 50% of hourly limit available (~105.8K / 210 K)
expect(await rToken.issuanceAvailable()).to.be.closeTo(fp('105800'), fp('100'))
expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo(
Expand All @@ -1473,20 +1473,16 @@ describe('Facade + FacadeMonitor contracts', () => {
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1'))

// Check redemptions
// Set redemption throttle to percent only
const redemptionThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } // 10%
await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams)

const totalSupply = await rToken.totalSupply()
expect(await rToken.redemptionAvailable()).to.equal(totalSupply.div(10)) // 10%
expect(await rToken.redemptionAvailable()).to.equal(totalSupply.div(5)) // 20%
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1'))

// Redeem half of the available throttle
await rToken.connect(addr1).redeem(totalSupply.div(10).div(2))

// About 52% now used of redemption throttle
expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.be.closeTo(
fp('0.52'),
fp('0.79'),
fp('0.01')
)

Expand Down
9 changes: 6 additions & 3 deletions test/Furnace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,12 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => {
await token3.connect(addr1).approve(rToken.address, max256)

// Set up larger throttles
const throttle = { amtRate: bal.lt(fp('1')) ? fp('1') : bal, pctRate: 0 }
await rToken.connect(owner).setIssuanceThrottleParams(throttle)
await rToken.connect(owner).setRedemptionThrottleParams(throttle)
const issuanceThrottle = { amtRate: bal.lt(fp('1')) ? fp('1') : bal, pctRate: 0 }
const redemptionThrottle = {
amtRate: bal.lt(fp('1')) ? fp('1').mul(125).div(100) : bal.mul(125).div(100),
pctRate: 0,
}
await rToken.connect(owner).setThrottleParams(issuanceThrottle, redemptionThrottle)
await advanceTime(3600)

// Issue and send tokens to furnace
Expand Down
Loading
Loading