Skip to content

Commit

Permalink
feat: long term limit order for SwapOperator (#1230)
Browse files Browse the repository at this point in the history
  • Loading branch information
rackstar authored Oct 2, 2024
2 parents fb7975b + 1fa7789 commit de0fe50
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 68 deletions.
7 changes: 6 additions & 1 deletion contracts/interfaces/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ struct Asset {

interface IPool {

error RevertedWithoutReason(uint index);
error AssetNotFound();
error UnknownParameter();
error OrderInProgress();

function swapOperator() external view returns (address);

function getAsset(uint assetId) external view returns (Asset memory);
Expand Down Expand Up @@ -52,5 +57,5 @@ interface IPool {

function getMCRRatio() external view returns (uint);

function setSwapValue(uint value) external;
function setSwapAssetAmount(address assetAddress, uint value) external;
}
2 changes: 1 addition & 1 deletion contracts/mocks/generic/PoolGeneric.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract PoolGeneric is IPool {
revert("Unsupported");
}

function setSwapValue(uint) external virtual pure {
function setSwapAssetAmount(address, uint) external virtual pure {
revert("Unsupported");
}
}
81 changes: 67 additions & 14 deletions contracts/modules/capital/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
// parameters
IPriceFeedOracle public override priceFeedOracle;
address public swapOperator;
uint96 public swapValue;

// SwapOperator assets
uint32 public assetsInSwapOperatorBitmap;
uint public assetInSwapOperator;

/* constants */

Expand Down Expand Up @@ -102,13 +105,13 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {

/* ========== ASSET RELATED VIEW FUNCTIONS ========== */

function getAssetValueInEth(address assetAddress) internal view returns (uint) {
function getAssetValueInEth(address assetAddress, uint assetAmountInSwapOperator) internal view returns (uint) {

uint assetBalance;
uint assetBalance = assetAmountInSwapOperator;

if (assetAddress.code.length != 0) {
try IERC20(assetAddress).balanceOf(address(this)) returns (uint balance) {
assetBalance = balance;
assetBalance += balance;
} catch {
// If balanceOf reverts consider it 0
}
Expand All @@ -127,17 +130,29 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
///
function getPoolValueInEth() public override view returns (uint) {

uint total = address(this).balance + swapValue;
uint total = address(this).balance;

uint assetCount = assets.length;
uint _assetsInSwapOperatorBitmap = assetsInSwapOperatorBitmap;

// Skip ETH (index 0)
for (uint i = 1; i < assetCount; i++) {
for (uint i = 0; i < assetCount; i++) {
Asset memory asset = assets[i];

if (assets[i].isAbandoned) {
if (asset.isAbandoned) {
continue;
}

uint assetAmountInSwapOperator = isAssetInSwapOperator(i, _assetsInSwapOperatorBitmap)
? assetInSwapOperator
: 0;

// check if the asset is ETH and skip the oracle call
if (i == 0) {
total += assetAmountInSwapOperator;
continue;
}

total += getAssetValueInEth(assets[i].assetAddress);
total += getAssetValueInEth(asset.assetAddress, assetAmountInSwapOperator);
}

return total;
Expand All @@ -156,6 +171,32 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
return swapDetails[assetAddress];
}

function getAssetId(address assetAddress) public view returns (uint) {

uint assetCount = assets.length;
for (uint i = 0; i < assetCount; i++) {
if (assets[i].assetAddress == assetAddress) {
return i;
}
}

revert AssetNotFound();
}

function isAssetInSwapOperator(uint _assetId, uint _assetsInSwapOperatorBitmap) internal pure returns (bool) {

if (
// there are assets in the swap operator
_assetsInSwapOperatorBitmap != 0 &&
// asset id is not in the swap operator assets
((1 << _assetId) & _assetsInSwapOperatorBitmap == 0)
) {
return false;
}

return true;
}

/* ========== ASSET RELATED MUTATIVE FUNCTIONS ========== */

function addAsset(
Expand Down Expand Up @@ -232,7 +273,7 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
return;
}

revert("Pool: Asset not found");
revert AssetNotFound();
}

function transferAsset(
Expand Down Expand Up @@ -275,8 +316,20 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
swapDetails[assetAddress].lastSwapTime = lastSwapTime;
}

function setSwapValue(uint newValue) external onlySwapOperator whenNotPaused {
swapValue = newValue.toUint96();
function setSwapAssetAmount(address assetAddress, uint value) external onlySwapOperator whenNotPaused {

uint assetId = getAssetId(assetAddress);
assetInSwapOperator = value;

if (value > 0) {
if (assetsInSwapOperatorBitmap != 0) {
revert OrderInProgress();
}

assetsInSwapOperatorBitmap = uint32(1 << assetId);
} else {
assetsInSwapOperatorBitmap = 0;
}
}

/* ========== CLAIMS RELATED MUTATIVE FUNCTIONS ========== */
Expand Down Expand Up @@ -429,7 +482,7 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
}

function updateUintParameters(bytes8 /* code */, uint /* value */) external view onlyGovernance {
revert("Pool: Unknown parameter");
revert UnknownParameter();
}

function updateAddressParameters(bytes8 code, address value) external onlyGovernance {
Expand All @@ -447,7 +500,7 @@ contract Pool is IPool, MasterAwareV2, ReentrancyGuard {
return;
}

revert("Pool: Unknown parameter");
revert UnknownParameter();
}

/* ========== DEPENDENCIES ========== */
Expand Down
25 changes: 11 additions & 14 deletions contracts/modules/capital/SwapOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract SwapOperator is ISwapOperator {
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint public constant MAX_SLIPPAGE_DENOMINATOR = 10000;
uint public constant MIN_VALID_TO_PERIOD = 600; // 10 minutes
uint public constant MAX_VALID_TO_PERIOD = 3600; // 60 minutes
uint public constant MAX_VALID_TO_PERIOD = 31 days; // 1 month
uint public constant MIN_TIME_BETWEEN_ORDERS = 900; // 15 minutes
uint public constant MAX_FEE = 0.3 ether;

Expand Down Expand Up @@ -284,38 +284,37 @@ contract SwapOperator is ISwapOperator {
/// Additionally if selling ETH, wraps received Pool ETH to WETH
function executeAssetTransfer(
IPool pool,
IPriceFeedOracle priceFeedOracle,
GPv2Order.Data calldata order,
SwapOperationType swapOperationType,
uint totalOutAmount
) internal returns (uint swapValueEth) {
) internal {
address sellTokenAddress = address(order.sellToken);
address buyTokenAddress = address(order.buyToken);

if (swapOperationType == SwapOperationType.EthToAsset) {
// set lastSwapTime of buyToken only (sellToken WETH has no set swapDetails)
pool.setSwapDetailsLastSwapTime(buyTokenAddress, uint32(block.timestamp));
// Set the setSwapAssetAmount on the pool
pool.setSwapAssetAmount(ETH, totalOutAmount);
// transfer ETH from pool and wrap it (use ETH address here because swapOp.sellToken is WETH address)
pool.transferAssetToSwapOperator(ETH, totalOutAmount);
weth.deposit{value: totalOutAmount}();
// no need to convert since totalOutAmount is already in ETH (i.e. WETH)
swapValueEth = totalOutAmount;
} else if (swapOperationType == SwapOperationType.AssetToEth) {
// set lastSwapTime of sellToken only (buyToken WETH has no set swapDetails)
pool.setSwapDetailsLastSwapTime(sellTokenAddress, uint32(block.timestamp));
// Set the setSwapAssetAmount on the pool
pool.setSwapAssetAmount(sellTokenAddress, totalOutAmount);
// transfer ERC20 asset from Pool
pool.transferAssetToSwapOperator(sellTokenAddress, totalOutAmount);
// convert totalOutAmount (sellAmount + fee) to ETH
swapValueEth = priceFeedOracle.getEthForAsset(sellTokenAddress, totalOutAmount);
} else {
// SwapOperationType.AssetToAsset
// set lastSwapTime of sell / buy tokens
pool.setSwapDetailsLastSwapTime(sellTokenAddress, uint32(block.timestamp));
pool.setSwapDetailsLastSwapTime(buyTokenAddress, uint32(block.timestamp));
// Set the setSwapAssetAmount on the pool
pool.setSwapAssetAmount(sellTokenAddress, totalOutAmount);
// transfer ERC20 asset from Pool
pool.transferAssetToSwapOperator(sellTokenAddress, totalOutAmount);
// convert totalOutAmount (sellAmount + fee) to ETH
swapValueEth = priceFeedOracle.getEthForAsset(sellTokenAddress, totalOutAmount);
}
}

Expand Down Expand Up @@ -343,10 +342,7 @@ contract SwapOperator is ISwapOperator {
performPreSwapValidations(pool, priceFeedOracle, order, swapOperationType, totalOutAmount);

// Execute swap based on operation type
uint swapValueEth = executeAssetTransfer(pool, priceFeedOracle, order, swapOperationType, totalOutAmount);

// Set the swapValue on the pool
pool.setSwapValue(swapValueEth);
executeAssetTransfer(pool, order, swapOperationType, totalOutAmount);

// Approve cowVaultRelayer contract to spend sellToken totalOutAmount
order.sellToken.safeApprove(cowVaultRelayer, totalOutAmount);
Expand Down Expand Up @@ -394,8 +390,9 @@ contract SwapOperator is ISwapOperator {
returnAssetToPool(pool, order.buyToken);
returnAssetToPool(pool, order.sellToken);

address sellToken = address(order.sellToken) == address(weth) ? ETH : address(order.sellToken);
// Set swapValue on pool to 0
pool.setSwapValue(0);
pool.setSwapAssetAmount(sellToken, 0);

// Emit event
emit OrderClosed(order, filledAmount);
Expand Down
1 change: 1 addition & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const PoolAsset = {
ETH: 0,
DAI: 1,
stETH: 2,
USDC: 6,
unknown: '115792089237316195423570985008687907853269984665640564039457584007913129639935',
};

Expand Down
2 changes: 1 addition & 1 deletion test/fork/basic-functionality-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ describe('basic functionality tests', function () {

it('Stake for assessment', async function () {
// stake
const amount = parseEther('500');
const amount = parseEther('200');
for (const abMember of this.abMembers.slice(0, ASSESSMENT_VOTER_COUNT)) {
const memberAddress = await abMember.getAddress();
const { amount: stakeAmountBefore } = await this.assessment.stakeOf(memberAddress);
Expand Down
Loading

0 comments on commit de0fe50

Please sign in to comment.