Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: long term limit order for SwapOperator #1230

Merged
merged 12 commits into from
Oct 2, 2024
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;
MilGard91 marked this conversation as resolved.
Show resolved Hide resolved

// 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);
MilGard91 marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading