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; 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 { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { IDelegatedVesting } from "@interfaces/IDelegatedVesting.sol"; import { ReentrancyGuard } from "@openzeppelin/security/ReentrancyGuard.sol";
import { add, sub, mul, wrap, unwrap, gt, mod, div } from "@prb/math/UD60x18.sol";
/* /*
* @title Rolling Dutch Auction (RDA) * @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 * @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 */ /* @dev Address mapping for an auction's redeemable balances */
mapping(address => mapping(bytes => bytes)) public _claims; mapping(address => mapping(bytes => bytes)) public _claims;
@ -28,8 +28,6 @@ contract RDA is IRDA {
/* @dev Auction mapping for the window index */ /* @dev Auction mapping for the window index */
mapping(bytes => uint256) public _windows; mapping(bytes => uint256) public _windows;
mapping(bytes => Vesting) public _vesting;
struct Auction { struct Auction {
uint256 windowDuration; /* @dev Unix time window duration */ uint256 windowDuration; /* @dev Unix time window duration */
uint256 windowTimestamp; /* @dev Unix timestamp for window start */ uint256 windowTimestamp; /* @dev Unix timestamp for window start */
@ -49,17 +47,14 @@ contract RDA is IRDA {
bool processed; /* @dev Window fuflfillment state */ bool processed; /* @dev Window fuflfillment state */
} }
struct Vesting {
address instance;
uint256 period;
}
/* /*
* @dev Conditioner to ensure an auction is active * @dev Conditioner to ensure an auction is active
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/ */
modifier activeAuction(bytes memory auctionId) { modifier activeAuction(bytes calldata auctionId) {
require(remainingWindowTime(auctionId) > 0 || remainingTime(auctionId) > 0); 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 * @dev Conditioner to ensure an auction is inactive
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/ */
modifier inactiveAuction(bytes memory auctionId) { modifier inactiveAuction(bytes calldata auctionId) {
require(remainingWindowTime(auctionId) == 0 && remainingTime(auctionId) == 0); 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 * @dev Helper to view an auction's operator address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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)); (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 * @dev Helper to view an auction's purchase token address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Ancoded auction parameter identifier * @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)); (,, 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 * @dev Helper to view an auction's reserve token address
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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)); (, 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 * @param w͟i͟n͟d͟o͟w͟D͟u͟r͟a͟t͟i͟o͟n͟ Uinx time window duration
*/ */
function createAuction( function createAuction(
address vestingAddress,
address operatorAddress, address operatorAddress,
address reserveToken, address reserveToken,
address purchaseToken, address purchaseToken,
@ -133,9 +145,8 @@ contract RDA is IRDA {
uint256 startingOriginPrice, uint256 startingOriginPrice,
uint256 startTimestamp, uint256 startTimestamp,
uint256 endTimestamp, uint256 endTimestamp,
uint256 windowDuration, uint256 windowDuration
uint256 vestingDuration ) override external returns (bytes memory) {
) external returns (bytes memory) {
bytes memory auctionId = abi.encode( bytes memory auctionId = abi.encode(
operatorAddress, operatorAddress,
reserveToken, reserveToken,
@ -144,24 +155,38 @@ contract RDA is IRDA {
abi.encodePacked(reserveAmount, startingOriginPrice, startTimestamp, endTimestamp, windowDuration) abi.encodePacked(reserveAmount, startingOriginPrice, startTimestamp, endTimestamp, windowDuration)
); );
ERC20 tokenReserve = ERC20(reserveToken);
ERC20 tokenPurchase = ERC20(purchaseToken);
Auction storage state = _auctions[auctionId]; Auction storage state = _auctions[auctionId];
uint256 auctionDuration = endTimestamp - startTimestamp;
if (state.price != 0) { if (state.price != 0) {
revert AuctionExists(); 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; tokenReserve.safeTransferFrom(msg.sender, address(this), reserveAmount);
_vesting[auctionId].period = vestingDuration;
IERC20(reserveToken).transferFrom(msg.sender, address(this), reserveAmount);
state.duration = endTimestamp - startTimestamp;
state.windowDuration = windowDuration; state.windowDuration = windowDuration;
state.windowTimestamp = startTimestamp; state.windowTimestamp = startTimestamp;
state.startTimestamp = startTimestamp; state.startTimestamp = startTimestamp;
state.endTimestamp = endTimestamp; state.endTimestamp = endTimestamp;
state.reserves = reserveAmount;
state.price = startingOriginPrice; state.price = startingOriginPrice;
state.duration = auctionDuration;
state.reserves = reserveAmount;
emit NewAuction(auctionId, reserveToken, reserveAmount, startingOriginPrice, endTimestamp); 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 * @dev Helper to view an auction's minimum purchase amount
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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)); (,,, 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 * @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; * 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 * 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 * @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]; Auction storage state = _auctions[auctionId];
Window storage window = _window[auctionId][_windows[auctionId]]; Window storage window = _window[auctionId][_windows[auctionId]];
bool isInitialised = window.expiry != 0; uint256 ts = isWindowExpired(auctionId) ? window.expiry : state.windowTimestamp;
bool isExpired = window.expiry < block.timestamp && isInitialised; 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); uint256 t_mod = t % (t_r - t);
UD60x18 t_r = wrap(state.duration - elapsedTime(auctionId, timestamp)); 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); return y - y_x / 1e18;
UD60x18 y = !isInitialised ? wrap(state.price) : wrap(window.price);
return sub(y, mul(y, x));
} }
/* /*
@ -218,9 +236,11 @@ contract RDA is IRDA {
* @param p͟r͟i͟c͟e͟ Bid order price * @param p͟r͟i͟c͟e͟ Bid order price
* @param v͟o͟l͟u͟m͟e͟ Bid order volume * @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) 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]]; Window storage window = _window[auctionId][_windows[auctionId]];
if (volume < minimumPurchase(auctionId)) { if (volume < minimumPurchase(auctionId)) {
@ -229,8 +249,8 @@ contract RDA is IRDA {
bool hasExpired; bool hasExpired;
if (window.expiry != 0) { if (isWindowInit(auctionId)) {
if (remainingWindowTime(auctionId) > 0) { if (isWindowActive(auctionId)) {
if (window.price < price) { if (window.price < price) {
if (volume < window.volume) { if (volume < window.volume) {
revert InvalidWindowVolume(); revert InvalidWindowVolume();
@ -244,68 +264,85 @@ contract RDA is IRDA {
} }
if (window.price == 0 || hasExpired) { if (window.price == 0 || hasExpired) {
if (gt(scalarPrice(auctionId), wrap(price))) { if (price < scalarPrice(auctionId)) {
revert InvalidScalarPrice(); 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(); 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]); (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) { if (hasExpired) {
window = _window[auctionId][windowExpiration(auctionId)]; window = _window[auctionId][windowExpiration(auctionId)];
} }
_auctions[auctionId].windowTimestamp = block.timestamp; window.expiry = block.timestamp + state.windowDuration;
window.volume = orderVolume;
window.expiry = block.timestamp + _auctions[auctionId].windowDuration;
window.volume = volume;
window.price = price; window.price = price;
window.bidId = bidId; 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 * @dev Expire and fulfill an auction's active window
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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 windowIndex = _windows[auctionId];
uint256 auctionElapsedTime = elapsedTime(auctionId, block.timestamp);
uint256 auctionRemainingTime = _auctions[auctionId].duration - auctionElapsedTime;
_auctions[auctionId].endTimestamp = block.timestamp + auctionRemainingTime; Auction storage state = _auctions[auctionId];
_auctions[auctionId].price = _window[auctionId][windowIndex].price; Window storage window = _window[auctionId][windowIndex];
state.endTimestamp = block.timestamp + remainingTime(auctionId);
state.price = window.price;
_windows[auctionId] = windowIndex + 1; _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; 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 * @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]; Window storage window = _window[auctionId][windowId];
if (window.expiry > block.timestamp) { if (isWindowActive(auctionId)) {
revert WindowUnexpired(); revert WindowUnexpired();
} }
if (window.processed) { if (window.processed) {
@ -319,10 +356,12 @@ contract RDA is IRDA {
window.processed = true; window.processed = true;
_auctions[auctionId].reserves -= volume / price; uint256 orderAmount = volume * 1e18 / price;
_auctions[auctionId].proceeds += volume;
_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); emit Fulfillment(auctionId, window.bidId, windowId);
} }
@ -331,53 +370,64 @@ contract RDA is IRDA {
* @dev Helper to view an auction's remaining duration * @dev Helper to view an auction's remaining duration
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/ */
function remainingTime(bytes memory auctionId) public view returns (uint256) { function remainingTime(bytes calldata auctionId) public view returns (uint256) {
uint256 endTimestamp = _auctions[auctionId].endTimestamp; return _auctions[auctionId].duration - elapsedTime(auctionId);
if (endTimestamp > block.timestamp) {
return endTimestamp - block.timestamp;
} else {
return 0;
}
} }
/* /*
* @dev Helper to view an auction's active remaining window duration * @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 * @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier
*/ */
function remainingWindowTime(bytes memory auctionId) public view returns (uint256) { function remainingWindowTime(bytes calldata auctionId) public view returns (uint256) {
uint256 expiryTimestamp = _window[auctionId][_windows[auctionId]].expiry; if (!isWindowActive(auctionId)) {
if (expiryTimestamp > 0 && block.timestamp < expiryTimestamp) {
return expiryTimestamp - block.timestamp;
} else {
return 0; return 0;
} }
return _window[auctionId][_windows[auctionId]].expiry - block.timestamp;
} }
/* /*
* @dev Helper to view an auction's progress in unix time * @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 * @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) { function elapsedTime(bytes calldata auctionId) public view returns (uint256) {
uint256 windowIndex = _windows[auctionId] + 1; return block.timestamp - windowElapsedTime(auctionId) - _auctions[auctionId].startTimestamp;
uint256 auctionElapsedTime = timestamp - _auctions[auctionId].startTimestamp;
uint256 windowElapsedTime = _auctions[auctionId].windowDuration * windowIndex;
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 * @dev Auction management redemption
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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) inactiveAuction(auctionId)
external { override external {
ERC20 tokenReserve = ERC20(reserveToken(auctionId));
ERC20 tokenPurchase = ERC20(purchaseToken(auctionId));
uint256 proceeds = _auctions[auctionId].proceeds; uint256 proceeds = _auctions[auctionId].proceeds;
uint256 reserves = _auctions[auctionId].reserves; uint256 reserves = _auctions[auctionId].reserves;
@ -385,10 +435,10 @@ contract RDA is IRDA {
delete _auctions[auctionId].reserves; delete _auctions[auctionId].reserves;
if (proceeds > 0) { if (proceeds > 0) {
IERC20(purchaseToken(auctionId)).transfer(operatorAddress(auctionId), proceeds); tokenPurchase.safeTransfer(operatorAddress(auctionId), proceeds);
} }
if (reserves > 0) { if (reserves > 0) {
IERC20(reserveToken(auctionId)).transfer(operatorAddress(auctionId), reserves); tokenReserve.safeTransfer(operatorAddress(auctionId), reserves);
} }
emit Withdraw(auctionId); emit Withdraw(auctionId);
@ -398,24 +448,23 @@ contract RDA is IRDA {
* @dev Auction order and refund redemption * @dev Auction order and refund redemption
* @param a͟u͟c͟t͟i͟o͟n͟I͟d͟ Encoded auction parameter identifier * @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) inactiveAuction(auctionId)
external { override external {
bytes memory claimHash = _claims[bidder][auctionId]; 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 refund, uint256 claim) = balancesOf(claimHash);
uint256 vestingTimestamp = block.timestamp + _vesting[auctionId].period; delete _claims[bidder][auctionId];
address vestingAddress = _vesting[auctionId].instance;
if (refund > 0) { if (refund > 0) {
IERC20(purchaseToken(auctionId)).transfer(bidder, refund); tokenPurchase.safeTransfer(bidder, refund);
} }
if (claim > 0) { if (claim > 0) {
IERC20(reserveToken(auctionId)).approve(vestingAddress, claim); tokenReserve.safeTransfer(bidder, claim);
IDelegatedVesting(vestingAddress).makeCommitment(bidder, claim, vestingTimestamp);
} }
emit Claim(auctionId, claimHash); emit Claim(auctionId, claimHash);