Skip to content

Commit

Permalink
CometBorrow (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
scott-silver committed Jul 12, 2024
1 parent 9168faf commit 3f0958e
Show file tree
Hide file tree
Showing 5 changed files with 1,156 additions and 5 deletions.
95 changes: 94 additions & 1 deletion src/builder/Actions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {Strings} from "./Strings.sol";
import {Accounts} from "./Accounts.sol";
import {CodeJarHelper} from "./CodeJarHelper.sol";

import {ApproveAndSwap, CometSupplyActions, CometWithdrawActions, TransferActions} from "../DeFiScripts.sol";
import {
ApproveAndSwap,
CometSupplyActions,
CometSupplyMultipleAssetsAndBorrow,
CometWithdrawActions,
TransferActions
} from "../DeFiScripts.sol";
import {WrapperActions} from "../WrapperScripts.sol";

import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol";
Expand Down Expand Up @@ -146,10 +152,12 @@ library Actions {

struct BorrowActionContext {
uint256 amount;
string assetSymbol;
uint256 chainId;
uint256[] collateralAmounts;
uint256[] collateralTokenPrices;
address[] collateralTokens;
string[] collateralAssetSymbols;
address comet;
uint256 price;
address token;
Expand Down Expand Up @@ -462,6 +470,91 @@ library Actions {
return (quarkOperation, action);
}

struct CometBorrowInput {
Accounts.ChainAccounts[] chainAccountsList;
uint256 amount;
string assetSymbol;
uint256 blockTimestamp;
address borrower;
uint256 chainId;
uint256[] collateralAmounts;
string[] collateralAssetSymbols;
address comet;
}

function cometBorrow(CometBorrowInput memory borrowInput, PaymentInfo.Payment memory payment)
internal
pure
returns (IQuarkWallet.QuarkOperation memory, Action memory)
{
bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = type(CometSupplyMultipleAssetsAndBorrow).creationCode;

Accounts.ChainAccounts memory accounts =
Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList);

Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates);

Accounts.AssetPositions memory borrowAssetPositions =
Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList);

uint256[] memory collateralTokenPrices = new uint256[](borrowInput.collateralAssetSymbols.length);
address[] memory collateralTokens = new address[](borrowInput.collateralAssetSymbols.length);

for (uint256 i = 0; i < borrowInput.collateralAssetSymbols.length; ++i) {
Accounts.AssetPositions memory assetPositions =
Accounts.findAssetPositions(borrowInput.collateralAssetSymbols[i], accounts.assetPositionsList);
collateralTokenPrices[i] = assetPositions.usdPrice;
collateralTokens[i] = assetPositions.asset;
}

// XXX handle wrapping ETH?
bytes memory scriptCalldata = abi.encodeWithSelector(
CometSupplyMultipleAssetsAndBorrow.run.selector,
borrowInput.comet,
collateralTokens,
borrowInput.collateralAmounts,
borrowAssetPositions.asset,
borrowInput.amount
);

// Construct QuarkOperation
IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({
nonce: accountState.quarkNextNonce,
scriptAddress: CodeJarHelper.getCodeAddress(type(CometSupplyMultipleAssetsAndBorrow).creationCode),
scriptCalldata: scriptCalldata,
scriptSources: scriptSources,
expiry: borrowInput.blockTimestamp + STANDARD_EXPIRY_BUFFER
});

// Construct Action
BorrowActionContext memory repayActionContext = BorrowActionContext({
assetSymbol: borrowInput.assetSymbol,
amount: borrowInput.amount,
chainId: borrowInput.chainId,
collateralAmounts: borrowInput.collateralAmounts,
collateralTokenPrices: collateralTokenPrices,
collateralTokens: collateralTokens,
collateralAssetSymbols: borrowInput.collateralAssetSymbols,
comet: borrowInput.comet,
price: borrowAssetPositions.usdPrice,
token: borrowAssetPositions.asset
});
Action memory action = Actions.Action({
chainId: borrowInput.chainId,
quarkAccount: borrowInput.borrower,
actionType: ACTION_TYPE_BORROW,
actionContext: abi.encode(repayActionContext),
paymentMethod: payment.isToken ? PAYMENT_METHOD_PAYCALL : PAYMENT_METHOD_OFFCHAIN,
// Null address for OFFCHAIN payment.
paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : address(0),
paymentTokenSymbol: payment.currency,
paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0
});

return (quarkOperation, action);
}

function cometSupplyAsset(CometSupply memory cometSupply, PaymentInfo.Payment memory payment)
internal
pure
Expand Down
176 changes: 176 additions & 0 deletions src/builder/QuarkBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,180 @@ contract QuarkBuilder {

/* ===== Main Implementation ===== */

struct CometBorrowIntent {
uint256 amount;
string assetSymbol;
uint256 blockTimestamp;
address borrower;
uint256 chainId;
uint256[] collateralAmounts;
string[] collateralAssetSymbols;
address comet;
}

function cometBorrow(
CometBorrowIntent memory borrowIntent,
Accounts.ChainAccounts[] memory chainAccountsList,
PaymentInfo.Payment memory payment
) external pure returns (BuilderResult memory /* builderResult */ ) {
if (borrowIntent.collateralAmounts.length != borrowIntent.collateralAssetSymbols.length) {
revert InvalidInput();
}

uint256 actionIndex = 0;
// max actions length = bridge each collateral asset + bridge payment token + perform borrow action
Actions.Action[] memory actions = new Actions.Action[](borrowIntent.collateralAssetSymbols.length + 1 + 1);
IQuarkWallet.QuarkOperation[] memory quarkOperations =
new IQuarkWallet.QuarkOperation[](chainAccountsList.length);

bool useQuotecall = false; // TODO: calculate an actual value for useQuoteCall
bool paymentTokenIsCollateralAsset = false;

for (uint256 i = 0; i < borrowIntent.collateralAssetSymbols.length; ++i) {
string memory assetSymbol = borrowIntent.collateralAssetSymbols[i];
uint256 supplyAmount = borrowIntent.collateralAmounts[i];

assertFundsAvailable(borrowIntent.chainId, assetSymbol, supplyAmount, chainAccountsList, payment);

if (Strings.stringEqIgnoreCase(assetSymbol, payment.currency)) {
paymentTokenIsCollateralAsset = true;
}

if (needsBridgedFunds(assetSymbol, supplyAmount, borrowIntent.chainId, chainAccountsList, payment)) {
// Note: Assumes that the asset uses the same # of decimals on each chain
uint256 amountNeededOnDst = supplyAmount;
// If action is paid for with tokens and the payment token is
// the supply token, we need to add the max cost to the
// amountLeftToBridge for target chain
if (payment.isToken && Strings.stringEqIgnoreCase(payment.currency, assetSymbol)) {
amountNeededOnDst += PaymentInfo.findMaxCost(payment, borrowIntent.chainId);
}
(IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) =
Actions.constructBridgeOperations(
Actions.BridgeOperationInfo({
assetSymbol: assetSymbol,
amountNeededOnDst: amountNeededOnDst,
dstChainId: borrowIntent.chainId,
recipient: borrowIntent.borrower,
blockTimestamp: borrowIntent.blockTimestamp,
useQuotecall: useQuotecall
}),
chainAccountsList,
payment
);

for (uint256 j = 0; j < bridgeQuarkOperations.length; ++j) {
quarkOperations[actionIndex] = bridgeQuarkOperations[j];
actions[actionIndex] = bridgeActions[j];
actionIndex++;
}
}
}

// when paying with tokens, you may need to bridge the payment token to cover the cost
if (payment.isToken && !paymentTokenIsCollateralAsset) {
uint256 maxCostOnDstChain = PaymentInfo.findMaxCost(payment, borrowIntent.chainId);
// but if you're borrowing the payment token, you can use the
// borrowed amount to cover the cost

if (Strings.stringEqIgnoreCase(payment.currency, borrowIntent.assetSymbol)) {
maxCostOnDstChain = Math.subtractFlooredAtZero(maxCostOnDstChain, borrowIntent.amount);
}

if (
needsBridgedFunds(payment.currency, maxCostOnDstChain, borrowIntent.chainId, chainAccountsList, payment)
) {
(IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) =
Actions.constructBridgeOperations(
Actions.BridgeOperationInfo({
assetSymbol: payment.currency,
amountNeededOnDst: maxCostOnDstChain,
dstChainId: borrowIntent.chainId,
recipient: borrowIntent.borrower,
blockTimestamp: borrowIntent.blockTimestamp,
useQuotecall: useQuotecall
}),
chainAccountsList,
payment
);

for (uint256 i = 0; i < bridgeQuarkOperations.length; ++i) {
quarkOperations[actionIndex] = bridgeQuarkOperations[i];
actions[actionIndex] = bridgeActions[i];
actionIndex++;
}
}
}

(quarkOperations[actionIndex], actions[actionIndex]) = Actions.cometBorrow(
Actions.CometBorrowInput({
chainAccountsList: chainAccountsList,
amount: borrowIntent.amount,
assetSymbol: borrowIntent.assetSymbol,
blockTimestamp: borrowIntent.blockTimestamp,
borrower: borrowIntent.borrower,
chainId: borrowIntent.chainId,
collateralAmounts: borrowIntent.collateralAmounts,
collateralAssetSymbols: borrowIntent.collateralAssetSymbols,
comet: borrowIntent.comet
}),
payment
);

actionIndex++;

// Truncate actions and quark operations
actions = Actions.truncate(actions, actionIndex);
quarkOperations = Actions.truncate(quarkOperations, actionIndex);

// Validate generated actions for affordability
if (payment.isToken) {
uint256 supplementalPaymentTokenBalance = 0;
if (Strings.stringEqIgnoreCase(payment.currency, borrowIntent.assetSymbol)) {
supplementalPaymentTokenBalance += borrowIntent.amount;
}

assertSufficientPaymentTokenBalances(
actions, chainAccountsList, borrowIntent.chainId, supplementalPaymentTokenBalance
);
}

// Merge operations that are from the same chain into one Multicall operation
(quarkOperations, actions) = QuarkOperationHelper.mergeSameChainOperations(quarkOperations, actions);

// Wrap operations around Paycall/Quotecall if payment is with token
if (payment.isToken) {
quarkOperations =
QuarkOperationHelper.wrapOperationsWithTokenPayment(quarkOperations, actions, payment, useQuotecall);
}

// Construct EIP712 digests
EIP712Helper.EIP712Data memory eip712Data;
if (quarkOperations.length == 1) {
eip712Data = EIP712Helper.EIP712Data({
digest: EIP712Helper.getDigestForQuarkOperation(
quarkOperations[0], actions[0].quarkAccount, actions[0].chainId
),
domainSeparator: EIP712Helper.getDomainSeparator(actions[0].quarkAccount, actions[0].chainId),
hashStruct: EIP712Helper.getHashStructForQuarkOperation(quarkOperations[0])
});
} else if (quarkOperations.length > 1) {
eip712Data = EIP712Helper.EIP712Data({
digest: EIP712Helper.getDigestForMultiQuarkOperation(quarkOperations, actions),
domainSeparator: EIP712Helper.MULTI_QUARK_OPERATION_DOMAIN_SEPARATOR,
hashStruct: EIP712Helper.getHashStructForMultiQuarkOperation(quarkOperations, actions)
});
}

return BuilderResult({
version: VERSION,
actions: actions,
quarkOperations: quarkOperations,
paymentCurrency: payment.currency,
eip712Data: eip712Data
});
}

struct CometSupplyIntent {
uint256 amount;
string assetSymbol;
Expand Down Expand Up @@ -874,6 +1048,8 @@ contract QuarkBuilder {
}
} else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_WITHDRAW)) {
continue;
} else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_BORROW)) {
continue;
} else {
revert InvalidActionType();
}
Expand Down
Loading

0 comments on commit 3f0958e

Please sign in to comment.