Skip to content

Commit

Permalink
CDAuctioneer: implement tracking of auction results over a defined pe…
Browse files Browse the repository at this point in the history
…riod of days
  • Loading branch information
0xJem committed Jan 14, 2025
1 parent 42420f3 commit 80aca68
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 70 deletions.
101 changes: 76 additions & 25 deletions src/policies/CDAuctioneer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
/// @dev These values should only be set through the `setAuctionParameters()` function
AuctionParameters internal _auctionParameters;

/// @notice Auction state at the time of the last bid (`_previousTick.lastUpdate`)
Day internal dayState;
/// @notice Auction state for the day
Day internal _dayState;

/// @notice Scale of the OHM token
uint256 internal constant _ohmScale = 1e9;
Expand Down Expand Up @@ -166,14 +166,9 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// Reject if the OHM out is 0
if (ohmOut == 0) revert CDAuctioneer_InvalidParams("converted amount");

// Reset the day state if this is the first bid of the day
if (block.timestamp / 86400 > _previousTick.lastUpdate / 86400) {
dayState = Day(0, 0);
}

// Update state
dayState.deposits += depositIn;
dayState.convertible += ohmOut;
_dayState.deposits += depositIn;
_dayState.convertible += ohmOut;

// Update current tick
_previousTick.price = currentTickPrice;
Expand Down Expand Up @@ -250,7 +245,7 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// The tick has also been depleted, so update the price
updatedTickPrice = _getNewTickPrice(updatedTickPrice, _tickStep);
updatedTickSize = _getNewTickSize(
dayState.convertible + convertibleAmount + ohmOut
_dayState.convertible + convertibleAmount + ohmOut
);
updatedTickCapacity = updatedTickSize;
}
Expand Down Expand Up @@ -396,7 +391,7 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function returns the day state at the time of the last bid (`_previousTick.lastUpdate`)
function getDayState() external view override returns (Day memory) {
return dayState;
return _dayState;
}

/// @inheritdoc IConvertibleDepositAuctioneer
Expand Down Expand Up @@ -433,12 +428,48 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// Min price must be non-zero
if (minPrice_ == 0) revert CDAuctioneer_InvalidParams("min price");

// Target must be non-zero
if (target_ == 0) revert CDAuctioneer_InvalidParams("target");

_auctionParameters = AuctionParameters(target_, tickSize_, minPrice_);

// Emit event
emit AuctionParametersUpdated(target_, tickSize_, minPrice_);
}

function _storeAuctionResults(uint256 previousTarget_) internal {
// Skip if inactive
if (!locallyActive) return;

// Skip if the day state was set on the same day
if (block.timestamp / 86400 <= _dayState.initTimestamp / 86400) return;

// If the next index is 0, reset the results before inserting
// This ensures that the previous results are available for 24 hours
if (_auctionResultsNextIndex == 0) {
_auctionResults = new int256[](_auctionTrackingPeriod);
}

// Store the auction results
// Negative values will indicate under-selling
_auctionResults[_auctionResultsNextIndex] =
int256(_dayState.convertible) -
int256(previousTarget_);

// Emit event
emit AuctionResult(_dayState.convertible, previousTarget_, _auctionResultsNextIndex);

// Increment the index (or loop around)
_auctionResultsNextIndex++;
// Loop around if necessary
if (_auctionResultsNextIndex >= _auctionTrackingPeriod) {
_auctionResultsNextIndex = 0;
}

// Reset the day state
_dayState = Day(uint48(block.timestamp), 0, 0);
}

/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function performs the following:
/// - Performs validation of the inputs
Expand All @@ -449,16 +480,13 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
/// - The caller does not have the ROLE_HEART role
/// - The new tick size is 0
/// - The new min price is 0
/// - The new target is 0
function setAuctionParameters(
uint256 target_,
uint256 tickSize_,
uint256 minPrice_
) external override onlyRole(ROLE_HEART) returns (uint256 remainder) {
// TODO should this be newTarget instead of _auctionParameters.target?
// TODO Should the newTarget - dayState.convertible be used instead?
// TODO how to handle if deactivated?
// remainder = (_auctionParameters.target > dayState.convertible) ? _auctionParameters.target - dayState.convertible : 0;
// TODO handling remainder moving average
uint256 previousTarget = _auctionParameters.target;

_setAuctionParameters(target_, tickSize_, minPrice_);

Expand All @@ -479,6 +507,9 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
_previousTick.price = minPrice_;
}

// Store the auction results, if necessary
_storeAuctionResults(previousTarget);

return remainder;
}

Expand Down Expand Up @@ -514,12 +545,22 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
emit TickStepUpdated(newStep_);
}

function setAuctionTrackingPeriod(uint8 days_) external onlyRole(ROLE_ADMIN) {
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function will revert if:
/// - The caller does not have the ROLE_ADMIN role
/// - The new auction tracking period is 0
///
/// @param days_ The new auction tracking period
function setAuctionTrackingPeriod(uint8 days_) public override onlyRole(ROLE_ADMIN) {
// Value must be non-zero
if (days_ == 0) revert CDAuctioneer_InvalidParams("auction tracking period");

_auctionTrackingPeriod = days_;

// Reset the auction results and index and set to the new length
_auctionResults = new int256[](days_);
_auctionResultsNextIndex = 0;

// Emit event
emit AuctionTrackingPeriodUpdated(days_);
}
Expand All @@ -529,6 +570,7 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function will revert if:
/// - The caller does not have the ROLE_ADMIN role
/// - The contract is already initialized
/// - The contract is already active
/// - Validation of the inputs fails
///
Expand All @@ -555,6 +597,10 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// This emits the event
setTimeToExpiry(timeToExpiry_);

// Set the auction tracking period
// This emits the event
setAuctionTrackingPeriod(auctionTrackingPeriod_);

// Initialize the current tick
_previousTick.capacity = tickSize_;
_previousTick.price = minPrice_;
Expand All @@ -572,8 +618,8 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// If not initialized, revert
if (!initialized) revert CDAuctioneer_NotInitialized();

// If the contract is already active, do nothing
if (locallyActive) return;
// If the contract is already active, revert
if (locallyActive) revert CDAuctioneer_InvalidState();

// Set the contract to active
locallyActive = true;
Expand All @@ -582,6 +628,13 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
// Otherwise, getCurrentTick() will calculate a long period of time having passed
_previousTick.lastUpdate = uint48(block.timestamp);

// Reset the day state
_dayState = Day(uint48(block.timestamp), 0, 0);

// Reset the auction results
_auctionResults = new int256[](_auctionTrackingPeriod);
_auctionResultsNextIndex = 0;

// Emit event
emit Activated();
}
Expand All @@ -590,20 +643,18 @@ contract CDAuctioneer is IConvertibleDepositAuctioneer, Policy, RolesConsumer, R
/// @dev This function will revert if:
/// - The caller does not have the ROLE_EMERGENCY_SHUTDOWN role
/// - The contract has not previously been initialized
///
/// Note that if the contract is already active, this function will do nothing.
/// - The contract is already active
function activate() external onlyRole(ROLE_EMERGENCY_SHUTDOWN) {
_activate();
}

/// @notice Deactivate the contract functionality
/// @dev This function will revert if:
/// - The caller does not have the ROLE_EMERGENCY_SHUTDOWN role
///
/// Note that if the contract is already inactive, this function will do nothing.
/// - The contract is already inactive
function deactivate() external onlyRole(ROLE_EMERGENCY_SHUTDOWN) {
// If the contract is already inactive, do nothing
if (!locallyActive) return;
// If the contract is already inactive, revert
if (!locallyActive) revert CDAuctioneer_InvalidState();

// Set the contract to inactive
locallyActive = false;
Expand Down
2 changes: 2 additions & 0 deletions src/policies/interfaces/IConvertibleDepositAuctioneer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ interface IConvertibleDepositAuctioneer {

/// @notice Tracks auction activity for a given day
///
/// @param initTimestamp Timestamp when the day state was initialized
/// @param deposits Quantity of bid tokens deposited for the day
/// @param convertible Quantity of OHM that will be issued for the day's deposits
struct Day {
uint48 initTimestamp;
uint256 deposits;
uint256 convertible;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ contract ConvertibleDepositAuctioneerTest is Test {
assertEq(auctionResults[6], resultSeven_, "result seven");
}

function _assertAuctionResultsEmpty(uint8 length_) internal {
int256[] memory auctionResults = auctioneer.getAuctionResults();

assertEq(auctionResults.length, length_, "auction results length");
for (uint256 i = 0; i < auctionResults.length; i++) {
assertEq(auctionResults[i], 0, string.concat("result ", vm.toString(i)));
}
}

function _assertAuctionResults(int256[] memory auctionResults_) internal {
int256[] memory auctionResults = auctioneer.getAuctionResults();

Expand Down
4 changes: 0 additions & 4 deletions src/test/policies/ConvertibleDepositAuctioneer/bid.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ contract ConvertibleDepositAuctioneerBidTest is ConvertibleDepositAuctioneerTest
function test_givenSpendingNotApproved_reverts()
public
givenInitialized
givenContractActive
givenAddressHasReserveToken(recipient, 1e18)
{
// Expect revert
Expand All @@ -172,7 +171,6 @@ contract ConvertibleDepositAuctioneerBidTest is ConvertibleDepositAuctioneerTest
function test_givenAuctioneerRoleNotGranted_reverts()
public
givenInitialized
givenContractActive
givenAddressHasReserveToken(recipient, 1e18)
givenReserveTokenSpendingIsApproved(recipient, address(convertibleDepository), 1e18)
{
Expand All @@ -192,7 +190,6 @@ contract ConvertibleDepositAuctioneerBidTest is ConvertibleDepositAuctioneerTest
)
public
givenInitialized
givenContractActive
givenAddressHasReserveToken(recipient, 1e18)
givenReserveTokenSpendingIsApproved(recipient, address(convertibleDepository), 1e18)
{
Expand All @@ -219,7 +216,6 @@ contract ConvertibleDepositAuctioneerBidTest is ConvertibleDepositAuctioneerTest
)
public
givenInitialized
givenContractActive
givenAddressHasReserveToken(recipient, 1e18)
givenReserveTokenSpendingIsApproved(recipient, address(convertibleDepository), 1e18)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ contract ConvertibleDepositAuctioneerInitializeTest is ConvertibleDepositAuction
// Assert state
assertEq(
auctioneer.getAuctionTrackingPeriod(),
AUCTION_TRACKING_PERIOD,
AUCTION_TRACKING_PERIOD + 1,
"auction tracking period"
);
assertEq(
Expand Down
Loading

0 comments on commit 80aca68

Please sign in to comment.