Skip to content
Open
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
60 changes: 53 additions & 7 deletions markets/treasury-market/contracts/TreasuryMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,44 @@ contract TreasuryMarket is ITreasuryMarket, Ownable, UUPSImplementation, IMarket
emit LoanAdjusted(accountId, accountDebt.toUint(), 0);
} else {
// this depositor is eligible for possible rewards
//DepositRewardConfiguration[] memory drc = depositRewardConfigurations;
for (uint256 i = 0; i < depositRewardConfigurations.length; i++) {
DepositRewardConfiguration memory config = depositRewardConfigurations[i];
uint256 rewardAmount = accountCollateral
.mulDecimal(oracleManager.process(config.valueRatioOracle).price.toUint())
.mulDecimal(config.percent);

DepositRewardConfiguration storage config = depositRewardConfigurations[i];

uint256 rewardAmount;
{
uint256 remainingRewardableCollateral = accountCollateral;

// we give the user as much rewards as we can give them until all rewards are exhausted
for (
uint256 j = 0;
j < config.amounts.length && remainingRewardableCollateral > 0;
j++
) {
if (config.amounts[j].maxCollateral == 0) {
continue;
}

uint256 rewardedCollateral = remainingRewardableCollateral <
config.amounts[j].maxCollateral
? remainingRewardableCollateral
: config.amounts[j].maxCollateral;

rewardAmount += rewardedCollateral
.mulDecimal(
oracleManager.process(config.valueRatioOracle).price.toUint()
)
.mulDecimal(config.amounts[j].percent);

remainingRewardableCollateral -= rewardedCollateral;
config.amounts[j].maxCollateral -= uint128(rewardedCollateral);
}

if (remainingRewardableCollateral > 0) {
// the user was probably expecting to get some rewards so go ahead and revert to ensure they do not enter the pool
// if they are not getting those rewards
revert InsufficientAvailableReward(config.token, accountCollateral, 0);
}
}
// stack was too deep to set this as a local variable. annoying.
depositRewards[accountId][config.token] = LoanInfo(
uint64(block.timestamp),
Expand Down Expand Up @@ -508,7 +539,22 @@ contract TreasuryMarket is ITreasuryMarket, Ownable, UUPSImplementation, IMarket
if (depositRewardConfigurations.length <= i) {
depositRewardConfigurations.push();
}
depositRewardConfigurations[i] = newDrcs[i];

// NOTE: not possible to simply copy to storage here because of limitation of solidity compiler
depositRewardConfigurations[i].token = newDrcs[i].token;
depositRewardConfigurations[i].power = newDrcs[i].power;
depositRewardConfigurations[i].duration = newDrcs[i].duration;
depositRewardConfigurations[i].valueRatioOracle = newDrcs[i].valueRatioOracle;
depositRewardConfigurations[i].penaltyStart = newDrcs[i].penaltyStart;
depositRewardConfigurations[i].penaltyEnd = newDrcs[i].penaltyEnd;

for (uint256 k = 0; k < depositRewardConfigurations[i].amounts.length; k++) {
depositRewardConfigurations[i].amounts.pop();
}
for (uint256 k = 0; k < newDrcs.length; k++) {
depositRewardConfigurations[i].amounts.push();
depositRewardConfigurations[i].amounts[k] = newDrcs[i].amounts[k];
}

// ensure that the v3 core system can pull funds from us
IERC20(newDrcs[i].token).approve(address(v3System), type(uint256).max);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ import "./external/IV3CoreProxy.sol";
* @title Synthetix V3 Market allowing for a trusted entity to manage excess liquidity allocated to a liquidity pool.
*/
interface ITreasuryMarket {
struct DepositRewardAmounts {
uint128 percent;
uint128 maxCollateral;
}

struct DepositRewardConfiguration {
address token;
uint32 power;
uint32 duration;
uint128 percent;
bytes32 valueRatioOracle;
uint128 penaltyStart;
uint128 penaltyEnd;
DepositRewardAmounts[] amounts;
}

struct LoanInfo {
Expand Down
130 changes: 113 additions & 17 deletions markets/treasury-market/test/TreasuryMarket.test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,33 +119,40 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
vm.prank(v3System.owner());
v3System.setFeatureFlagAllowAll("associateDebt", true);

ITreasuryMarket.DepositRewardAmounts[]
memory amnts = new ITreasuryMarket.DepositRewardAmounts[](2);
amnts[0].maxCollateral = 1 ether;
amnts[0].percent = 0.2 ether;
amnts[1].maxCollateral = 99 ether;
amnts[1].percent = 0.2 ether;

ITreasuryMarket.DepositRewardConfiguration[]
memory dcr = new ITreasuryMarket.DepositRewardConfiguration[](2);
dcr[0] = ITreasuryMarket.DepositRewardConfiguration({
token: address(collateralToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});
dcr[1] = ITreasuryMarket.DepositRewardConfiguration({
token: address(usdToken),
power: 2,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 0.0 ether,
penaltyEnd: 0.0 ether
penaltyEnd: 0.0 ether,
amounts: amnts
});

vm.startPrank(v3System.owner());
Expand Down Expand Up @@ -229,7 +236,28 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
market.saddle(accountId + 1);
}

function test__RevertIf_SaddleAccountWithNoDelegatedCollateral() public {
function test_RevertIf_SaddleRewardsExhausted() external {
// delegate another account to cause the avg c-ratio to shoot way up5000000000000000000
v3System.delegateCollateral(
accountId,
poolId,
address(collateralToken),
101 ether,
1 ether
);

vm.expectRevert(
abi.encodeWithSelector(
ITreasuryMarket.InsufficientAvailableReward.selector,
address(collateralToken),
101 ether,
0
)
);
market.saddle(accountId);
}

function test_RevertIf_SaddleAccountWithNoDelegatedCollateral() public {
vm.expectRevert(
abi.encodeWithSelector(
ParameterError.InvalidParameter.selector,
Expand Down Expand Up @@ -314,6 +342,17 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
);
}

function test_RevertIf_DepositRewardPenaltyNotSet() external {
vm.expectRevert(
abi.encodeWithSelector(
ParameterError.InvalidParameter.selector,
"depositRewardToken",
"config not found"
)
);
market.depositRewardPenalty(accountId, address(132612361236512));
}

function test_SaddleSecondAccount() external {
// saddle the first account
sideMarket.setReportedDebt(1 ether);
Expand Down Expand Up @@ -532,6 +571,15 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
assertEq(market.repaymentPenalty(accountId, 0), 0);
}

function test_RepaymentPenaltyNoTimeElapse() external {
vm.prank(market.owner());
market.setDebtDecayFunction(1, 0, 1 ether, 0.5 ether);
sideMarket.setReportedDebt(1 ether);
market.saddle(accountId);

assertEq(market.repaymentPenalty(accountId, 0), 0);
}

function test_RepayLoanMidScheduleLinearWithPenalty() external {
vm.warp(100000000);
vm.prank(market.owner());
Expand Down Expand Up @@ -662,6 +710,14 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
market.mintTreasury(1 ether);
}

function test_RevertIf_TreasuryMintExcess() external {
vm.prank(market.treasury());
vm.expectRevert(
abi.encodeWithSelector(ITreasuryMarket.InsufficientExcessDebt.selector, 1000 ether, 0)
);
market.mintTreasury(1000 ether);
}

function test_TreasuryMint() external {
market.saddle(accountId);

Expand Down Expand Up @@ -721,11 +777,23 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
assertEq(market.availableDepositRewards(address(collateralToken)), 0);
}

function test__RevertIf_RemoveDepositRewardUnauthorized() external {
function test_RevertIf_RemoveDepositRewardUnauthorized() external {
vm.expectRevert(abi.encodeWithSelector(AccessError.Unauthorized.selector, address(this)));
market.removeFromDepositReward(address(collateralToken), 500 ether);
}

function test_RevertIf_RemoveDepositRewardExcess() external {
vm.prank(market.owner());
vm.expectRevert(
abi.encodeWithSelector(
ParameterError.InvalidParameter.selector,
"amount",
"greater than available rewards"
)
);
market.removeFromDepositReward(address(collateralToken), 1000.1 ether);
}

function test_RevertIf_UnsaddleUnauthorized() external {
market.saddle(accountId);

Expand Down Expand Up @@ -1016,7 +1084,7 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
market.unsaddle(1337);
}

function test__RevertIf_UnsaddleTooBigForReward() public {
function test_RevertIf_UnsaddleTooBigForReward() public {
market.saddle(accountId);
sideMarket.setReportedDebt(1 ether);

Expand Down Expand Up @@ -1137,6 +1205,13 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
market.setDepositRewardConfigurations(dcr);
vm.stopPrank();

ITreasuryMarket.DepositRewardAmounts[]
memory amnts = new ITreasuryMarket.DepositRewardAmounts[](2);
amnts[0].maxCollateral = 1 ether;
amnts[0].percent = 0.2 ether;
amnts[1].maxCollateral = 100000000 ether;
amnts[1].percent = 0.2 ether;

ITreasuryMarket.DepositRewardConfiguration[]
memory configs = new ITreasuryMarket.DepositRewardConfiguration[](2);

Expand All @@ -1147,28 +1222,28 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
token: address(usdToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});

configs[1] = ITreasuryMarket.DepositRewardConfiguration({
token: address(collateralToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});

vm.prank(market.owner());
Expand All @@ -1185,21 +1260,28 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
function test_SetDepositConfigurationOneRewardProperlyRemoved() public {
bytes32[] memory parents = new bytes32[](0);

ITreasuryMarket.DepositRewardAmounts[]
memory amnts = new ITreasuryMarket.DepositRewardAmounts[](2);
amnts[0].maxCollateral = 1 ether;
amnts[0].percent = 0.2 ether;
amnts[1].maxCollateral = 100000000 ether;
amnts[1].percent = 0.2 ether;

// remove the usd deposit reward configuration
ITreasuryMarket.DepositRewardConfiguration[]
memory configs = new ITreasuryMarket.DepositRewardConfiguration[](1);
configs[0] = ITreasuryMarket.DepositRewardConfiguration({
token: address(usdToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});
vm.startPrank(market.owner());
market.removeFromDepositReward(address(collateralToken), 1000 ether);
Expand All @@ -1210,21 +1292,28 @@ contract TreasuryMarketTest is Test, IERC721Receiver {
function test_SetDepositConfigurationOneRewardRugRemoved() public {
bytes32[] memory parents = new bytes32[](0);

ITreasuryMarket.DepositRewardAmounts[]
memory amnts = new ITreasuryMarket.DepositRewardAmounts[](2);
amnts[0].maxCollateral = 1 ether;
amnts[0].percent = 0.2 ether;
amnts[1].maxCollateral = 100000000 ether;
amnts[1].percent = 0.2 ether;

// remove the usd deposit reward configuration
ITreasuryMarket.DepositRewardConfiguration[]
memory configs = new ITreasuryMarket.DepositRewardConfiguration[](1);
configs[0] = ITreasuryMarket.DepositRewardConfiguration({
token: address(usdToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});
vm.prank(market.owner());
vm.expectRevert(
Expand Down Expand Up @@ -1252,21 +1341,28 @@ contract TreasuryMarketTest is Test, IERC721Receiver {

bytes32[] memory parents = new bytes32[](0);

ITreasuryMarket.DepositRewardAmounts[]
memory amnts = new ITreasuryMarket.DepositRewardAmounts[](2);
amnts[0].maxCollateral = 1 ether;
amnts[0].percent = 0.2 ether;
amnts[1].maxCollateral = 100000000 ether;
amnts[1].percent = 0.2 ether;

// remove the usd deposit reward configuration
ITreasuryMarket.DepositRewardConfiguration[]
memory configs = new ITreasuryMarket.DepositRewardConfiguration[](1);
configs[0] = ITreasuryMarket.DepositRewardConfiguration({
token: address(collateralToken),
power: 1,
duration: 86400,
percent: 0.2 ether,
valueRatioOracle: NodeModule(0x83A0444B93927c3AFCbe46E522280390F748E171).registerNode(
NodeDefinition.NodeType.CHAINLINK,
abi.encode(address(mockAggregator), uint256(0), uint8(18)),
parents
),
penaltyStart: 1.0 ether,
penaltyEnd: 0.5 ether
penaltyEnd: 0.5 ether,
amounts: amnts
});

vm.prank(market.owner());
Expand Down