Skip to content

Commit

Permalink
feat: transmuter with hardcaps
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeNervoXS committed May 10, 2024
1 parent 6aecf1a commit a7a6d84
Show file tree
Hide file tree
Showing 18 changed files with 323 additions and 23 deletions.
3 changes: 3 additions & 0 deletions contracts/interfaces/IGetters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,7 @@ interface IGetters {

/// @notice Gets the data needed to deal with whitelists for `collateral`
function getCollateralWhitelistData(address collateral) external view returns (bytes memory);

/// @notice Returns the stablecoin cap for `collateral`
function getStablecoinCap(address collateral) external view returns (uint256);
}
3 changes: 3 additions & 0 deletions contracts/interfaces/ISetters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,7 @@ interface ISettersGuardian {

/// @notice Changes the whitelist status for a collateral with `whitelistType` for an address `who`
function toggleWhitelist(WhitelistType whitelistType, address who) external;

/// @notice Sets the stablecoin cap that can be issued from a `collateral`
function setStablecoinCap(address collateral, uint256 stablecoinCap) external;
}
1 change: 1 addition & 0 deletions contracts/transmuter/Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ struct Collateral {
bytes oracleConfig; // Data about the oracle used for the collateral
bytes whitelistData; // For whitelisted collateral, data used to verify whitelists
ManagerStorage managerData; // For managed collateral, data used to handle the strategies
uint256 stablecoinCap; // Cap on the amount of stablecoins that can be issued from this collateral
}

struct TransmuterStorage {
Expand Down
5 changes: 5 additions & 0 deletions contracts/transmuter/configs/Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ contract Test {
LibSetters.setFees(eurY.collateral, xBurnFee, yBurnFee, false);
LibSetters.togglePause(eurY.collateral, ActionType.Burn);

// Set no hard limits on stablecoin minting per collateral
LibSetters.setStablecoinCap(eurA.collateral, type(uint256).max);
LibSetters.setStablecoinCap(eurB.collateral, type(uint256).max);
LibSetters.setStablecoinCap(eurY.collateral, type(uint256).max);

// Redeem
LibSetters.togglePause(eurA.collateral, ActionType.Redeem);
}
Expand Down
5 changes: 5 additions & 0 deletions contracts/transmuter/facets/Getters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,9 @@ contract Getters is IGetters {
function getCollateralWhitelistData(address collateral) external view returns (bytes memory) {
return s.transmuterStorage().collaterals[collateral].whitelistData;
}

/// @inheritdoc IGetters
function getStablecoinCap(address collateral) external view returns (uint256) {
return s.transmuterStorage().collaterals[collateral].stablecoinCap;
}
}
5 changes: 5 additions & 0 deletions contracts/transmuter/facets/SettersGuardian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ contract SettersGuardian is AccessControlModifiers, ISettersGuardian {
function toggleWhitelist(WhitelistType whitelistType, address who) external onlyGuardian {
LibSetters.toggleWhitelist(whitelistType, who);
}

/// @inheritdoc ISettersGuardian
function setStablecoinCap(address collateral, uint256 stablecoinCap) external onlyGuardian {
LibSetters.setStablecoinCap(collateral, stablecoinCap);
}
}
49 changes: 34 additions & 15 deletions contracts/transmuter/facets/Swapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,25 @@ contract Swapper is ISwapper, AccessControlModifiers {

/// @inheritdoc ISwapper
function quoteIn(uint256 amountIn, address tokenIn, address tokenOut) external view returns (uint256 amountOut) {
TransmuterStorage storage ts = s.transmuterStorage();
(bool mint, Collateral storage collatInfo) = _getMintBurn(tokenIn, tokenOut, 0);
if (mint) return _quoteMintExactInput(collatInfo, amountIn);
else {
if (mint) {
amountOut = _quoteMintExactInput(collatInfo, amountIn);
_checkHardCaps(collatInfo, amountOut, ts.normalizer);
} else {
amountOut = _quoteBurnExactInput(tokenOut, collatInfo, amountIn);
_checkAmounts(tokenOut, collatInfo, amountOut);
}
}

/// @inheritdoc ISwapper
function quoteOut(uint256 amountOut, address tokenIn, address tokenOut) external view returns (uint256 amountIn) {
TransmuterStorage storage ts = s.transmuterStorage();
(bool mint, Collateral storage collatInfo) = _getMintBurn(tokenIn, tokenOut, 0);
if (mint) return _quoteMintExactOutput(collatInfo, amountOut);
else {
if (mint) {
_checkHardCaps(collatInfo, amountOut, ts.normalizer);
return _quoteMintExactOutput(collatInfo, amountOut);
} else {
_checkAmounts(tokenOut, collatInfo, amountOut);
return _quoteBurnExactOutput(tokenOut, collatInfo, amountOut);
}
Expand All @@ -187,6 +193,7 @@ contract Swapper is ISwapper, AccessControlModifiers {
if (amountIn > 0 && amountOut > 0) {
TransmuterStorage storage ts = s.transmuterStorage();
if (mint) {
_checkHardCaps(collatInfo, amountOut, ts.normalizer);
uint128 changeAmount = (amountOut.mulDiv(BASE_27, ts.normalizer, Math.Rounding.Up)).toUint128();
// The amount of stablecoins issued from a collateral are not stored as absolute variables, but
// as variables normalized by a `normalizer`
Expand Down Expand Up @@ -354,9 +361,11 @@ contract Swapper is ISwapper, AccessControlModifiers {
}
{
// In the mint case, when `!v.isExact`: = `b_{i+1} * (1+(g_i(0)+f_{i+1})/2)`
uint256 amountToNextBreakPointNormalizer = v.isExact ? v.amountToNextBreakPoint : v.isMint
? _invertFeeMint(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2)
: _applyFeeBurn(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2);
uint256 amountToNextBreakPointNormalizer = v.isExact

Check failure on line 364 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Replace ⏎····················?·v.amountToNextBreakPoint⏎··················· with ·?·v.amountToNextBreakPoint
? v.amountToNextBreakPoint
: v.isMint
? _invertFeeMint(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2)

Check failure on line 367 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Delete ····
: _applyFeeBurn(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2);

Check failure on line 368 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Delete ····

if (amountToNextBreakPointNormalizer >= amountStable) {
int64 midFee;
Expand Down Expand Up @@ -426,9 +435,11 @@ contract Swapper is ISwapper, AccessControlModifiers {
return amount + _computeFee(quoteType, amountStable, midFee);
} else {
amountStable -= amountToNextBreakPointNormalizer;
amount += !v.isExact ? v.amountToNextBreakPoint : v.isMint
? _invertFeeMint(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2)
: _applyFeeBurn(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2);
amount += !v.isExact

Check failure on line 438 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Replace ⏎························?·v.amountToNextBreakPoint⏎······················· with ·?·v.amountToNextBreakPoint
? v.amountToNextBreakPoint
: v.isMint
? _invertFeeMint(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2)

Check failure on line 441 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Delete ····
: _applyFeeBurn(v.amountToNextBreakPoint, int64(v.upperFees + currentFees) / 2);

Check failure on line 442 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Delete ····
currentExposure = v.upperExposure * BASE_9;
++i;
// Update for the rest of the swaps the stablecoins issued from the asset
Expand All @@ -452,6 +463,12 @@ contract Swapper is ISwapper, AccessControlModifiers {
) revert InvalidSwap();
}

/// @notice Checks whether there is enough space left to mint from this collateral
function _checkHardCaps(Collateral storage collatInfo, uint256 amount, uint256 normalizer) internal view {
if (amount + (collatInfo.normalizedStables * normalizer) / BASE_27 > collatInfo.stablecoinCap)
revert InvalidSwap();
}

/// @notice Checks whether a swap from `tokenIn` to `tokenOut` is a mint or a burn, whether the
/// collateral provided is paused or not and in case of whether the swap is not occuring too late
/// @dev The function reverts if the `tokenIn` and `tokenOut` given do not correspond to the stablecoin
Expand Down Expand Up @@ -521,11 +538,13 @@ contract Swapper is ISwapper, AccessControlModifiers {
/// @notice Applies or inverts `fees` to an `amount` based on the type of operation
function _computeFee(QuoteType quoteType, uint256 amount, int64 fees) internal pure returns (uint256) {
return
quoteType == QuoteType.MintExactInput ? _applyFeeMint(amount, fees) : quoteType == QuoteType.MintExactOutput
? _invertFeeMint(amount, fees)
: quoteType == QuoteType.BurnExactInput
? _applyFeeBurn(amount, fees)
: _invertFeeBurn(amount, fees);
quoteType == QuoteType.MintExactInput

Check failure on line 541 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Replace ⏎················?·_applyFeeMint(amount,·fees)⏎··············· with ·?·_applyFeeMint(amount,·fees)
? _applyFeeMint(amount, fees)
: quoteType == QuoteType.MintExactOutput
? _invertFeeMint(amount, fees)

Check failure on line 544 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Replace ···················· with ················
: quoteType == QuoteType.BurnExactInput

Check failure on line 545 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Delete ····
? _applyFeeBurn(amount, fees)

Check failure on line 546 in contracts/transmuter/facets/Swapper.sol

View workflow job for this annotation

GitHub Actions / lint

Replace ························ with ················
: _invertFeeBurn(amount, fees);
}

/// @notice Checks whether an operation is a mint operation or not
Expand Down
9 changes: 9 additions & 0 deletions contracts/transmuter/libraries/LibSetters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ library LibSetters {
event PauseToggled(address indexed collateral, uint256 pausedType, bool isPaused);
event RedemptionCurveParamsSet(uint64[] xFee, int64[] yFee);
event ReservesAdjusted(address indexed collateral, uint256 amount, bool increase);
event StablecoinCapSet(address indexed collateral, uint256 stablecoinCap);
event TrustedToggled(address indexed sender, bool isTrusted, TrustedType trustedType);
event WhitelistStatusToggled(WhitelistType whitelistType, address indexed who, uint256 whitelistStatus);

Expand Down Expand Up @@ -209,6 +210,14 @@ library LibSetters {
emit WhitelistStatusToggled(whitelistType, who, whitelistStatus);
}

/// @notice Sets the stablecoin cap that can be issued from a collateral
function setStablecoinCap(address collateral, uint256 stablecoinCap) internal {
Collateral storage collatInfo = s.transmuterStorage().collaterals[collateral];
if (collatInfo.decimals == 0) revert NotCollateral();
collatInfo.stablecoinCap = stablecoinCap;
emit StablecoinCapSet(collateral, stablecoinCap);
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ via_ir = true
sizes = true
optimizer = true
optimizer_runs=1000
solc_version = '0.8.22'
solc_version = '0.8.23'
ffi = true
fs_permissions = [
{ access = "read-write", path = "./scripts/selectors.json"},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"test:invariant": "forge test -vvv --gas-report --match-path \"test/invariants/**/*.sol\"",
"test:fuzz": "forge test -vvv --gas-report --match-path \"test/fuzz/**/*.sol\"",
"slither": "chmod +x ./slither.sh && ./slither.sh",
"test": "forge test -vvvv",
"test": "forge test -vvv",
"lcov:clean": "lcov --remove lcov.info -o lcov.info 'test/**' 'scripts/**' 'contracts/transmuter/configs/**' 'contracts/utils/**'",
"lcov:generate-html": "genhtml lcov.info --output=coverage",
"size": "forge build --skip test --sizes",
Expand Down
5 changes: 4 additions & 1 deletion scripts/selectors_add.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"SettersGovernor": [
"0x1cb44dfc00000000000000000000000000000000000000000000000000000000"
],
"SettersGuardian": [
"0x603b432700000000000000000000000000000000000000000000000000000000"
],
"useless": ""
}
}
17 changes: 13 additions & 4 deletions scripts/test/UpdateTransmuterFacets.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ contract UpdateTransmuterFacetsTest is Helpers, Test {
addFacetNames.push("SettersGovernor");
addFacetAddressList.push(settersGovernor);

addFacetNames.push("SettersGuardian");
address settersGuardian = address(new SettersGuardian());
addFacetAddressList.push(settersGuardian);

string memory jsonReplace = vm.readFile(JSON_SELECTOR_PATH_REPLACE);
{
// Build appropriate payload
Expand Down Expand Up @@ -310,6 +314,11 @@ contract UpdateTransmuterFacetsTest is Helpers, Test {

transmuter.toggleWhitelist(Storage.WhitelistType.BACKED, WHALE_AGEUR);

// Set no hard limits on stablecoin minting per collateral
transmuter.setStablecoinCap(EUROC, type(uint256).max);
transmuter.setStablecoinCap(BC3M, type(uint256).max);
transmuter.setStablecoinCap(BERNX, type(uint256).max);

vm.stopPrank();
}

Expand Down Expand Up @@ -341,7 +350,7 @@ contract UpdateTransmuterFacetsTest is Helpers, Test {
assertEq(collatInfoEUROC.isBurnLive, 1);
assertEq(collatInfoEUROC.decimals, 6);
assertEq(collatInfoEUROC.onlyWhitelisted, 0);
assertApproxEqRel(collatInfoEUROC.normalizedStables, 9580108 * BASE_18, 100 * BPS);
assertApproxEqRel(collatInfoEUROC.normalizedStables, 10593543 * BASE_18, 100 * BPS);
assertEq(collatInfoEUROC.oracleConfig, oracleConfigEUROC);
assertEq(collatInfoEUROC.whitelistData.length, 0);
assertEq(collatInfoEUROC.managerData.subCollaterals.length, 0);
Expand Down Expand Up @@ -537,7 +546,7 @@ contract UpdateTransmuterFacetsTest is Helpers, Test {
function testUnit_Upgrade_GetCollateralRatio() external {
(uint64 collatRatio, uint256 stablecoinIssued) = transmuter.getCollateralRatio();
assertApproxEqRel(collatRatio, 1065 * 10 ** 6, BPS * 100);
assertApproxEqRel(stablecoinIssued, 15816758 * BASE_18, 100 * BPS);
assertApproxEqRel(stablecoinIssued, 16816758 * BASE_18, 100 * BPS);
}

function testUnit_Upgrade_isTrusted() external {
Expand Down Expand Up @@ -580,8 +589,8 @@ contract UpdateTransmuterFacetsTest is Helpers, Test {

function testUnit_Upgrade_getOracleValues_Success() external {
_checkOracleValues(address(EUROC), BASE_18, USER_PROTECTION_EUROC, FIREWALL_BURN_RATIO_EUROC);
_checkOracleValues(address(BC3M), (11974 * BASE_18) / 100, USER_PROTECTION_BC3M, FIREWALL_BURN_RATIO_BC3M);
_checkOracleValues(address(BERNX), (52274 * BASE_18) / 10000, USER_PROTECTION_ERNX, FIREWALL_BURN_RATIO_ERNX);
_checkOracleValues(address(BC3M), (11951 * BASE_18) / 100, USER_PROTECTION_BC3M, FIREWALL_BURN_RATIO_BC3M);
_checkOracleValues(address(BERNX), (52164 * BASE_18) / 10000, USER_PROTECTION_ERNX, FIREWALL_BURN_RATIO_ERNX);
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
9 changes: 9 additions & 0 deletions scripts/test/UpdateTransmuterFacetsUSDATest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ contract UpdateTransmuterFacetsUSDATest is Helpers, Test {
addFacetNames.push("SettersGovernor");
addFacetAddressList.push(settersGovernor);

addFacetNames.push("SettersGuardian");
address settersGuardian = address(new SettersGuardian());
addFacetAddressList.push(settersGuardian);

string memory jsonReplace = vm.readFile(JSON_SELECTOR_PATH_REPLACE);
{
// Build appropriate payload
Expand Down Expand Up @@ -308,6 +312,11 @@ contract UpdateTransmuterFacetsUSDATest is Helpers, Test {
transmuter.toggleTrusted(NEW_DEPLOYER, Storage.TrustedType.Seller);
transmuter.toggleTrusted(NEW_KEEPER, Storage.TrustedType.Seller);

// Set no hard limits on stablecoin minting per collateral
transmuter.setStablecoinCap(USDC, type(uint256).max);
transmuter.setStablecoinCap(BIB01, type(uint256).max);
transmuter.setStablecoinCap(STEAK_USDC, type(uint256).max);

vm.stopPrank();
}

Expand Down
Loading

0 comments on commit a7a6d84

Please sign in to comment.