bump RDA version

This commit is contained in:
gozzy 2023-04-09 12:33:02 +00:00
parent 6beafd9b35
commit 5b13dfe581
2 changed files with 165 additions and 115 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sol linguist-language=Solidity

View File

@ -1,12 +1,10 @@
pragma solidity 0.8.13;
import { UD60x18 } from "@prb/math/UD60x18.sol";
import { IRDA } from "@root/interfaces/IRDA.sol";
import { IRDA } from "@interfaces/IRDA.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
import { IDelegatedVesting } from "@interfaces/IDelegatedVesting.sol";
import { add, sub, mul, wrap, unwrap, gt, mod, div } from "@prb/math/UD60x18.sol";
import { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/security/ReentrancyGuard.sol";
/*
* @title Rolling Dutch Auction (RDA)
@ -14,7 +12,9 @@ import { add, sub, mul, wrap, unwrap, gt, mod, div } from "@prb/math/UD60x18.sol
* @description A dutch auction derivative with composite decay
*/
contract RDA is IRDA {
contract RDA is IRDA, ReentrancyGuard {
using SafeERC20 for ERC20;
/* @dev Address mapping for an auction's redeemable balances */
mapping(address => mapping(bytes => bytes)) public _claims;
@ -28,13 +28,11 @@ contract RDA is IRDA {
/* @dev Auction mapping for the window index */
mapping(bytes => uint256) public _windows;
mapping(bytes => Vesting) public _vesting;
struct Auction {
uint256 windowDuration; /* @dev Unix time window duration */
uint256 windowTimestamp; /* @dev Unix timestamp for window start */
uint256 startTimestamp; /* @dev Unix auction start timestamp */
uint256 endTimestamp; /* @dev Unix auction end timestamp */
uint256 endTimestamp; /* @dev Unix auction end timestamp */
uint256 duration; /* @dev Unix time auction duration */
uint256 proceeds; /* @dev Auction proceeds balance */
uint256 reserves; /* @dev Auction reserves balance */
@ -49,17 +47,14 @@ contract RDA is IRDA {
bool processed; /* @dev Window fuflfillment state */
}
struct Vesting {
address instance;
uint256 period;
}
/*
* @dev Conditioner to ensure an auction is active
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
modifier activeAuction(bytes memory auctionId) {
require(remainingWindowTime(auctionId) > 0 || remainingTime(auctionId) > 0);
modifier activeAuction(bytes calldata auctionId) {
if (remainingWindowTime(auctionId) == 0 && remainingTime(auctionId) == 0) {
revert AuctionInactive();
}
_;
}
@ -67,8 +62,10 @@ contract RDA is IRDA {
* @dev Conditioner to ensure an auction is inactive
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
modifier inactiveAuction(bytes memory auctionId) {
require(remainingWindowTime(auctionId) == 0 && remainingTime(auctionId) == 0);
modifier inactiveAuction(bytes calldata auctionId) {
if (remainingWindowTime(auctionId) > 0 || remainingTime(auctionId) > 0) {
revert AuctionActive();
}
_;
}
@ -76,7 +73,7 @@ contract RDA is IRDA {
* @dev Helper to view an auction's operator address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function operatorAddress(bytes memory auctionId) public pure returns (address opAddress) {
function operatorAddress(bytes calldata auctionId) public pure returns (address opAddress) {
(opAddress,,,,) = abi.decode(auctionId, (address, address, address, uint256, bytes));
}
@ -84,15 +81,31 @@ contract RDA is IRDA {
* @dev Helper to view an auction's purchase token address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Ancoded auction parameter identifier
*/
function purchaseToken(bytes memory auctionId) public pure returns (address tokenAddress) {
function purchaseToken(bytes calldata auctionId) public pure returns (address tokenAddress) {
(,, tokenAddress,,) = abi.decode(auctionId, (address, address, address, uint256, bytes));
}
function isWindowInit(bytes calldata auctionId) public view returns (bool) {
return _window[auctionId][_windows[auctionId]].expiry != 0;
}
function isWindowActive(bytes calldata auctionId) public view returns (bool) {
Window storage window = _window[auctionId][_windows[auctionId]];
return isWindowInit(auctionId) && window.expiry > block.timestamp;
}
function isWindowExpired(bytes calldata auctionId) public view returns (bool) {
Window storage window = _window[auctionId][_windows[auctionId]];
return isWindowInit(auctionId) && window.expiry < block.timestamp;
}
/*
* @dev Helper to view an auction's reserve token address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function reserveToken(bytes memory auctionId) public pure returns (address tokenAddress) {
function reserveToken(bytes calldata auctionId) public pure returns (address tokenAddress) {
(, tokenAddress,,,) = abi.decode(auctionId, (address, address, address, uint256, bytes));
}
@ -124,7 +137,6 @@ contract RDA is IRDA {
* @param w͟i͟n͟d͟o͟w͟D͟u͟r͟a͟t͟i͟o͟n͟ Uinx time window duration
*/
function createAuction(
address vestingAddress,
address operatorAddress,
address reserveToken,
address purchaseToken,
@ -133,9 +145,8 @@ contract RDA is IRDA {
uint256 startingOriginPrice,
uint256 startTimestamp,
uint256 endTimestamp,
uint256 windowDuration,
uint256 vestingDuration
) external returns (bytes memory) {
uint256 windowDuration
) override external returns (bytes memory) {
bytes memory auctionId = abi.encode(
operatorAddress,
reserveToken,
@ -144,24 +155,38 @@ contract RDA is IRDA {
abi.encodePacked(reserveAmount, startingOriginPrice, startTimestamp, endTimestamp, windowDuration)
);
ERC20 tokenReserve = ERC20(reserveToken);
ERC20 tokenPurchase = ERC20(purchaseToken);
Auction storage state = _auctions[auctionId];
uint256 auctionDuration = endTimestamp - startTimestamp;
if (state.price != 0) {
revert AuctionExists();
}
if (startingOriginPrice == 0) {
revert InvalidAuctionPrice();
}
if (startTimestamp < block.timestamp) {
revert InvalidAuctionTimestamp();
}
if (tokenReserve.decimals() != tokenPurchase.decimals()){
revert InvalidTokenDecimals();
}
if (auctionDuration < 1 days || windowDuration < 2 hours) {
revert InvalidAuctionDurations();
}
_vesting[auctionId].instance = vestingAddress;
_vesting[auctionId].period = vestingDuration;
tokenReserve.safeTransferFrom(msg.sender, address(this), reserveAmount);
IERC20(reserveToken).transferFrom(msg.sender, address(this), reserveAmount);
state.duration = endTimestamp - startTimestamp;
state.windowDuration = windowDuration;
state.windowTimestamp = startTimestamp;
state.startTimestamp = startTimestamp;
state.endTimestamp = endTimestamp;
state.reserves = reserveAmount;
state.price = startingOriginPrice;
state.duration = auctionDuration;
state.reserves = reserveAmount;
emit NewAuction(auctionId, reserveToken, reserveAmount, startingOriginPrice, endTimestamp);
@ -172,18 +197,10 @@ contract RDA is IRDA {
* @dev Helper to view an auction's minimum purchase amount
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function minimumPurchase(bytes memory auctionId) public pure returns (uint256 minimumAmount) {
function minimumPurchase(bytes calldata auctionId) public pure returns (uint256 minimumAmount) {
(,,, minimumAmount,) = abi.decode(auctionId, (address, address, address, uint256, bytes));
}
/*
* @dev Helper to view an auction's active scalar price formatted to uint256
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function scalarPriceUint(bytes calldata auctionId) external returns (uint256) {
return unwrap(scalarPrice(auctionId));
}
/*
* @dev Active price decay proportional to time delta (t) between the current
* timestamp and the window's start timestamp or if the window is expired;
@ -194,22 +211,23 @@ contract RDA is IRDA {
* the origin price (y) and subtracted by y to result the decayed price
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function scalarPrice(bytes memory auctionId) public view returns (UD60x18) {
function scalarPrice(bytes calldata auctionId)
activeAuction(auctionId)
public view returns (uint256) {
Auction storage state = _auctions[auctionId];
Window storage window = _window[auctionId][_windows[auctionId]];
bool isInitialised = window.expiry != 0;
bool isExpired = window.expiry < block.timestamp && isInitialised;
uint256 ts = isWindowExpired(auctionId) ? window.expiry : state.windowTimestamp;
uint256 y = !isWindowInit(auctionId) ? state.price : window.price;
uint256 timestamp = isExpired ? window.expiry : state.windowTimestamp;
uint256 t = block.timestamp - ts;
uint256 t_r = state.duration - elapsedTimeFromWindow(auctionId);
UD60x18 t = wrap(block.timestamp - timestamp);
UD60x18 t_r = wrap(state.duration - elapsedTime(auctionId, timestamp));
uint256 t_mod = t % (t_r - t);
uint256 x = (t + t_mod) * 1e18;
uint256 y_x = y * x / t_r;
UD60x18 x = div(add(t, mod(t, sub(t_r, t))), t_r);
UD60x18 y = !isInitialised ? wrap(state.price) : wrap(window.price);
return sub(y, mul(y, x));
return y - y_x / 1e18;
}
/*
@ -218,9 +236,11 @@ contract RDA is IRDA {
* @param p͟r͟i͟c͟e͟ Bid order price
* @param v͟o͟l͟u͟m͟e͟ Bid order volume
*/
function commitBid(bytes memory auctionId, uint256 price, uint256 volume)
function commitBid(bytes calldata auctionId, uint256 price, uint256 volume)
activeAuction(auctionId)
external returns (bytes memory) {
nonReentrant
override external returns (bytes memory bidId) {
Auction storage state = _auctions[auctionId];
Window storage window = _window[auctionId][_windows[auctionId]];
if (volume < minimumPurchase(auctionId)) {
@ -229,8 +249,8 @@ contract RDA is IRDA {
bool hasExpired;
if (window.expiry != 0) {
if (remainingWindowTime(auctionId) > 0) {
if (isWindowInit(auctionId)) {
if (isWindowActive(auctionId)) {
if (window.price < price) {
if (volume < window.volume) {
revert InvalidWindowVolume();
@ -244,68 +264,85 @@ contract RDA is IRDA {
}
if (window.price == 0 || hasExpired) {
if (gt(scalarPrice(auctionId), wrap(price))) {
if (price < scalarPrice(auctionId)) {
revert InvalidScalarPrice();
}
}
IERC20(purchaseToken(auctionId)).transferFrom(msg.sender, address(this), volume);
uint256 orderVolume = volume - (volume % price);
if (_auctions[auctionId].reserves < (volume / price)) {
if (state.reserves < orderVolume * 1e18 / price) {
revert InsufficientReserves();
}
if (volume < price) {
revert InvalidReserveVolume();
}
bytes memory bidId = abi.encode(auctionId, msg.sender, price, volume);
bidId = abi.encode(auctionId, msg.sender, price, orderVolume);
(uint256 refund, uint256 claim) = balancesOf(_claims[msg.sender][auctionId]);
_claims[msg.sender][auctionId] = abi.encode(refund + volume, claim);
_claims[msg.sender][auctionId] = abi.encode(refund + orderVolume, claim);
if (hasExpired) {
window = _window[auctionId][windowExpiration(auctionId)];
}
_auctions[auctionId].windowTimestamp = block.timestamp;
window.expiry = block.timestamp + _auctions[auctionId].windowDuration;
window.volume = volume;
window.expiry = block.timestamp + state.windowDuration;
window.volume = orderVolume;
window.price = price;
window.bidId = bidId;
emit Offer(auctionId, msg.sender, window.bidId, window.expiry);
state.windowTimestamp = block.timestamp;
return bidId;
emit Offer(auctionId, msg.sender, bidId, window.expiry);
ERC20 tokenPurchase = ERC20(purchaseToken(auctionId));
tokenPurchase.safeTransferFrom(msg.sender, address(this), orderVolume);
}
/*
* @dev Expire and fulfill an auction's active window
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function windowExpiration(bytes memory auctionId) internal returns (uint256) {
function windowExpiration(bytes calldata auctionId) internal returns (uint256) {
uint256 windowIndex = _windows[auctionId];
uint256 auctionElapsedTime = elapsedTime(auctionId, block.timestamp);
uint256 auctionRemainingTime = _auctions[auctionId].duration - auctionElapsedTime;
_auctions[auctionId].endTimestamp = block.timestamp + auctionRemainingTime;
_auctions[auctionId].price = _window[auctionId][windowIndex].price;
Auction storage state = _auctions[auctionId];
Window storage window = _window[auctionId][windowIndex];
state.endTimestamp = block.timestamp + remainingTime(auctionId);
state.price = window.price;
_windows[auctionId] = windowIndex + 1;
fulfillWindow(auctionId, windowIndex);
_fulfillWindow(auctionId, windowIndex);
emit Expiration(auctionId, _window[auctionId][windowIndex].bidId, windowIndex);
emit Expiration(auctionId, window.bidId, windowIndex);
return windowIndex + 1;
}
/*
* @dev Fulfill a window index even if the auction is inactive
* @dev Fulfill a window index for an inactive auction
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function fulfillWindow(bytes memory auctionId, uint256 windowId) public {
function fulfillWindow(bytes calldata auctionId, uint256 windowId)
inactiveAuction(auctionId)
override public {
_fulfillWindow(auctionId, windowId);
}
/*
* @dev Fulfill a window index
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function _fulfillWindow(bytes calldata auctionId, uint256 windowId) internal {
Auction storage state = _auctions[auctionId];
Window storage window = _window[auctionId][windowId];
if (window.expiry > block.timestamp) {
if (isWindowActive(auctionId)) {
revert WindowUnexpired();
}
if (window.processed) {
@ -319,10 +356,12 @@ contract RDA is IRDA {
window.processed = true;
_auctions[auctionId].reserves -= volume / price;
_auctions[auctionId].proceeds += volume;
uint256 orderAmount = volume * 1e18 / price;
_claims[bidder][auctionId] = abi.encode(refund - volume, claim + (volume / price));
state.reserves -= orderAmount;
state.proceeds += volume;
_claims[bidder][auctionId] = abi.encode(refund - volume, claim + orderAmount);
emit Fulfillment(auctionId, window.bidId, windowId);
}
@ -331,53 +370,64 @@ contract RDA is IRDA {
* @dev Helper to view an auction's remaining duration
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function remainingTime(bytes memory auctionId) public view returns (uint256) {
uint256 endTimestamp = _auctions[auctionId].endTimestamp;
if (endTimestamp > block.timestamp) {
return endTimestamp - block.timestamp;
} else {
return 0;
}
function remainingTime(bytes calldata auctionId) public view returns (uint256) {
return _auctions[auctionId].duration - elapsedTime(auctionId);
}
/*
* @dev Helper to view an auction's active remaining window duration
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function remainingWindowTime(bytes memory auctionId) public view returns (uint256) {
uint256 expiryTimestamp = _window[auctionId][_windows[auctionId]].expiry;
if (expiryTimestamp > 0 && block.timestamp < expiryTimestamp) {
return expiryTimestamp - block.timestamp;
} else {
function remainingWindowTime(bytes calldata auctionId) public view returns (uint256) {
if (!isWindowActive(auctionId)) {
return 0;
}
}
return _window[auctionId][_windows[auctionId]].expiry - block.timestamp;
}
/*
* @dev Helper to view an auction's progress in unix time
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function elapsedTime(bytes memory auctionId, uint256 timestamp) public view returns (uint256) {
uint256 windowIndex = _windows[auctionId] + 1;
uint256 auctionElapsedTime = timestamp - _auctions[auctionId].startTimestamp;
uint256 windowElapsedTime = _auctions[auctionId].windowDuration * windowIndex;
function elapsedTime(bytes calldata auctionId) public view returns (uint256) {
return block.timestamp - windowElapsedTime(auctionId) - _auctions[auctionId].startTimestamp;
}
if (auctionElapsedTime > windowElapsedTime) {
return auctionElapsedTime - windowElapsedTime;
} else {
return auctionElapsedTime;
function windowElapsedTime(bytes calldata auctionId) public view returns (uint256) {
if (!isWindowInit(auctionId)) {
return 0;
}
uint256 windowIndex = _windows[auctionId];
uint256 elapsedWindowsTime = _auctions[auctionId].windowDuration * (windowIndex + 1);
return elapsedWindowsTime - remainingWindowTime(auctionId);
}
function elapsedTimeFromWindow(bytes calldata auctionId) public view returns (uint256) {
Auction storage state = _auctions[auctionId];
uint256 endTimestamp = state.windowTimestamp;
if (isWindowExpired(auctionId)) {
endTimestamp = _window[auctionId][_windows[auctionId]].expiry;
}
return endTimestamp - windowElapsedTime(auctionId) - state.startTimestamp;
}
/*
* @dev Auction management redemption
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function withdraw(bytes memory auctionId)
function withdraw(bytes calldata auctionId)
inactiveAuction(auctionId)
external {
override external {
ERC20 tokenReserve = ERC20(reserveToken(auctionId));
ERC20 tokenPurchase = ERC20(purchaseToken(auctionId));
uint256 proceeds = _auctions[auctionId].proceeds;
uint256 reserves = _auctions[auctionId].reserves;
@ -385,10 +435,10 @@ contract RDA is IRDA {
delete _auctions[auctionId].reserves;
if (proceeds > 0) {
IERC20(purchaseToken(auctionId)).transfer(operatorAddress(auctionId), proceeds);
tokenPurchase.safeTransfer(operatorAddress(auctionId), proceeds);
}
if (reserves > 0) {
IERC20(reserveToken(auctionId)).transfer(operatorAddress(auctionId), reserves);
tokenReserve.safeTransfer(operatorAddress(auctionId), reserves);
}
emit Withdraw(auctionId);
@ -398,24 +448,23 @@ contract RDA is IRDA {
* @dev Auction order and refund redemption
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/
function redeem(address bidder, bytes memory auctionId)
function redeem(address bidder, bytes calldata auctionId)
inactiveAuction(auctionId)
external {
bytes memory claimHash = _claims[bidder][auctionId];
override external {
ERC20 tokenReserve = ERC20(reserveToken(auctionId));
ERC20 tokenPurchase = ERC20(purchaseToken(auctionId));
delete _claims[bidder][auctionId];
bytes memory claimHash = _claims[bidder][auctionId];
(uint256 refund, uint256 claim) = balancesOf(claimHash);
uint256 vestingTimestamp = block.timestamp + _vesting[auctionId].period;
address vestingAddress = _vesting[auctionId].instance;
delete _claims[bidder][auctionId];
if (refund > 0) {
IERC20(purchaseToken(auctionId)).transfer(bidder, refund);
tokenPurchase.safeTransfer(bidder, refund);
}
if (claim > 0) {
IERC20(reserveToken(auctionId)).approve(vestingAddress, claim);
IDelegatedVesting(vestingAddress).makeCommitment(bidder, claim, vestingTimestamp);
tokenReserve.safeTransfer(bidder, claim);
}
emit Claim(auctionId, claimHash);