Skip to content

Commit

Permalink
claim reward builder code
Browse files Browse the repository at this point in the history
  • Loading branch information
cwang25 committed Sep 10, 2024
1 parent ca598a9 commit 255d19f
Show file tree
Hide file tree
Showing 4 changed files with 526 additions and 2 deletions.
93 changes: 91 additions & 2 deletions src/builder/Actions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CometWithdrawActions,
TransferActions
} from "../DeFiScripts.sol";
import {Math} from "src/lib/Math.sol";
import {MorphoActions, MorphoRewardsActions, MorphoVaultActions} from "../MorphoScripts.sol";
import {WrapperActions} from "../WrapperScripts.sol";
import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol";
Expand All @@ -29,7 +30,9 @@ library Actions {
string constant ACTION_TYPE_BORROW = "BORROW";
string constant ACTION_TYPE_MORPHO_BORROW = "MORPHO_BORROW";
string constant ACTION_TYPE_BRIDGE = "BRIDGE";
// TODO: (LHT-86) Rename ACTION_TYPE_CLAIM_REWARDS to ACTION_TYPE_COMET_CLAIM_REWARDS
string constant ACTION_TYPE_CLAIM_REWARDS = "CLAIM_REWARDS";
string constant ACTION_TYPE_MORPHO_CLAIM_REWARDS = "MORPHO_CLAIM_REWARDS";
string constant ACTION_TYPE_DRIP_TOKENS = "DRIP_TOKENS";
// TODO: (LHT-86) Rename ACTION_TYPE_REPAY to ACTION_TYPE_COMET_REPAY, as now we have more than one borrow market
string constant ACTION_TYPE_REPAY = "REPAY";
Expand Down Expand Up @@ -194,6 +197,18 @@ library Actions {
address withdrawer;
}

struct MorphoClaimRewards {
Accounts.ChainAccounts[] chainAccountsList;
address[] accounts;
uint256 blockTimestamp;
uint256 chainId;
uint256[] claimables;
address claimer;
address[] distributors;
address[] rewards;
bytes32[][] proofs;
}

// Note: Mainly to avoid stack too deep errors
struct BridgeOperationInfo {
string assetSymbol;
Expand Down Expand Up @@ -399,6 +414,14 @@ library Actions {
address token;
}

struct MorphoClaimRewardsActionContext {
uint256[] amounts;
string[] assetSymbols;
uint256 chainId;
uint256[] prices;
address[] tokens;
}

function constructBridgeOperations(
BridgeOperationInfo memory bridgeInfo,
Accounts.ChainAccounts[] memory chainAccountsList,
Expand Down Expand Up @@ -490,8 +513,9 @@ library Actions {
} else {
// NOTE: This logic only works when the user has only a single account on each chain. If there are multiple,
// then we need to re-adjust this.
amountToBridge =
srcAccountBalances[j].balance - PaymentInfo.findMaxCost(payment, srcChainAccounts.chainId);
amountToBridge = Math.subtractFlooredAtZero(
srcAccountBalances[j].balance, PaymentInfo.findMaxCost(payment, srcChainAccounts.chainId)
);
}
} else {
if (srcAccountBalances[j].balance >= amountLeftToBridge) {
Expand Down Expand Up @@ -1186,6 +1210,71 @@ library Actions {
return (quarkOperation, action);
}

function morphoClaimRewards(MorphoClaimRewards memory claimRewards, PaymentInfo.Payment memory payment)
internal
pure
returns (IQuarkWallet.QuarkOperation memory, Action memory)
{
bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = type(MorphoRewardsActions).creationCode;

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

Accounts.QuarkState memory accountState = Accounts.findQuarkState(claimRewards.claimer, accounts.quarkStates);

List.DynamicArray memory rewardsPriceList = List.newList();
List.DynamicArray memory rewardsSymbolList = List.newList();
for (uint256 i = 0; i < claimRewards.rewards.length; i++) {
Accounts.AssetPositions memory rewardsAssetPosition =
Accounts.findAssetPositions(claimRewards.rewards[i], accounts.assetPositionsList);
List.addUint256(rewardsPriceList, rewardsAssetPosition.usdPrice);
List.addString(rewardsSymbolList, rewardsAssetPosition.symbol);
}

bytes memory scriptCalldata = abi.encodeWithSelector(
MorphoRewardsActions.claimAll.selector,
claimRewards.distributors,
claimRewards.accounts,
claimRewards.rewards,
claimRewards.claimables,
claimRewards.proofs
);

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

MorphoClaimRewardsActionContext memory claimRewardsActionContext = MorphoClaimRewardsActionContext({
amounts: claimRewards.claimables,
assetSymbols: List.toStringArray(rewardsSymbolList),
chainId: claimRewards.chainId,
prices: List.toUint256Array(rewardsPriceList),
tokens: claimRewards.rewards
});

Action memory action = Actions.Action({
chainId: claimRewards.chainId,
quarkAccount: claimRewards.claimer,
actionType: ACTION_TYPE_MORPHO_CLAIM_REWARDS,
actionContext: abi.encode(claimRewardsActionContext),
paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false),
// Null address for OFFCHAIN payment.
paymentToken: payment.isToken
? PaymentInfo.knownToken(payment.currency, claimRewards.chainId).token
: address(0),
paymentTokenSymbol: payment.currency,
paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, claimRewards.chainId) : 0
});

return (quarkOperation, action);
}

function wrapOrUnwrapAsset(
WrapOrUnwrapAsset memory wrapOrUnwrap,
PaymentInfo.Payment memory payment,
Expand Down
52 changes: 52 additions & 0 deletions src/builder/List.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,56 @@ library List {
function contains(DynamicArray memory list, Actions.Action memory action) internal pure returns (bool) {
return contains(list, abi.encode(action));
}

// === uint256 ===

function addUint256(DynamicArray memory list, uint256 item) internal pure returns (DynamicArray memory) {
return addItem(list, abi.encode(item));
}

function getUint256(DynamicArray memory list, uint256 index) internal pure returns (uint256) {
return abi.decode(get(list, index), (uint256));
}

function toUint256Array(DynamicArray memory list) internal pure returns (uint256[] memory) {
uint256[] memory result = new uint256[](list.length);
for (uint256 i = 0; i < list.length; ++i) {
result[i] = abi.decode(list.bytesArray[i], (uint256));
}
return result;
}

function indexOf(DynamicArray memory list, uint256 item) internal pure returns (int256) {
return indexOf(list, abi.encode(item));
}

function contains(DynamicArray memory list, uint256 item) internal pure returns (bool) {
return contains(list, abi.encode(item));
}

// === String ===

function addString(DynamicArray memory list, string memory item) internal pure returns (DynamicArray memory) {
return addItem(list, abi.encode(item));
}

function getString(DynamicArray memory list, uint256 index) internal pure returns (string memory) {
return abi.decode(get(list, index), (string));
}

function toStringArray(DynamicArray memory list) internal pure returns (string[] memory) {
string[] memory result = new string[](list.length);
for (uint256 i = 0; i < list.length; ++i) {
result[i] = abi.decode(list.bytesArray[i], (string));
}
return result;
}

function indexOf(DynamicArray memory list, string memory item) internal pure returns (int256) {
return indexOf(list, abi.encode(item));
}

function contains(DynamicArray memory list, string memory item) internal pure returns (bool) {
return contains(list, abi.encode(item));
}
}
137 changes: 137 additions & 0 deletions src/builder/QuarkBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,140 @@ contract QuarkBuilder {
});
}

struct MorphoRewardsClaimIntent {
uint256 blockTimestamp;
address claimer;
uint256 chainId;
address[] accounts;
uint256[] claimables;
address[] distributors;
address[] rewards;
bytes32[][] proofs;
}

function morphoClaimRewards(
MorphoRewardsClaimIntent memory claimIntent,
Accounts.ChainAccounts[] memory chainAccountsList,
PaymentInfo.Payment memory payment
) external pure returns (BuilderResult memory) {
if (
claimIntent.accounts.length != claimIntent.claimables.length
|| claimIntent.accounts.length != claimIntent.distributors.length
|| claimIntent.accounts.length != claimIntent.rewards.length
|| claimIntent.accounts.length != claimIntent.proofs.length
) {
revert InvalidInput();
}

bool useQuotecall = false; // never use Quotecall
List.DynamicArray memory actions = List.newList();
List.DynamicArray memory quarkOperations = List.newList();

// when paying with tokens, you may need to bridge the payment token to cover the cost
if (payment.isToken) {
uint256 maxCostOnDstChain = PaymentInfo.findMaxCost(payment, claimIntent.chainId);
// if you're claiming rewards in payment token, you can use the withdrawn amount to cover the cost
for (uint256 i = 0; i < claimIntent.rewards.length; ++i) {
if (
Strings.stringEqIgnoreCase(
payment.currency,
Accounts.findAssetPositions(claimIntent.rewards[i], claimIntent.chainId, chainAccountsList)
.symbol
)
) {
maxCostOnDstChain = Math.subtractFlooredAtZero(maxCostOnDstChain, claimIntent.claimables[i]);
}
}

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

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

(IQuarkWallet.QuarkOperation memory cometWithdrawQuarkOperation, Actions.Action memory cometWithdrawAction) =
Actions.morphoClaimRewards(
Actions.MorphoClaimRewards({
chainAccountsList: chainAccountsList,
accounts: claimIntent.accounts,
blockTimestamp: claimIntent.blockTimestamp,
chainId: claimIntent.chainId,
claimables: claimIntent.claimables,
claimer: claimIntent.claimer,
distributors: claimIntent.distributors,
rewards: claimIntent.rewards,
proofs: claimIntent.proofs
}),
payment
);
List.addAction(actions, cometWithdrawAction);
List.addQuarkOperation(quarkOperations, cometWithdrawQuarkOperation);

// Convert actions and quark operations to arrays
Actions.Action[] memory actionsArray = List.toActionArray(actions);
IQuarkWallet.QuarkOperation[] memory quarkOperationsArray = List.toQuarkOperationArray(quarkOperations);

// Validate generated actions for affordability
if (payment.isToken) {
uint256 supplementalPaymentTokenBalance = 0;
for (uint256 i = 0; i < claimIntent.rewards.length; ++i) {
if (
Strings.stringEqIgnoreCase(
payment.currency,
Accounts.findAssetPositions(claimIntent.rewards[i], claimIntent.chainId, chainAccountsList)
.symbol
)
) {
supplementalPaymentTokenBalance += claimIntent.claimables[i];
}
}

assertSufficientPaymentTokenBalances(
actionsArray,
chainAccountsList,
claimIntent.chainId,
claimIntent.claimer,
supplementalPaymentTokenBalance
);
}

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

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

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

// For some reason, funds that may otherwise be bridgeable or held by the user cannot
// be made available to fulfill the transaction.
// Funds cannot be bridged, e.g. no bridge exists
Expand Down Expand Up @@ -1817,6 +1951,9 @@ contract QuarkBuilder {
continue;
} else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_BORROW)) {
continue;
} else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_CLAIM_REWARDS))
{
continue;
} else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_REPAY)) {
Actions.MorphoRepayActionContext memory morphoRepayActionContext =
abi.decode(nonBridgeAction.actionContext, (Actions.MorphoRepayActionContext));
Expand Down
Loading

0 comments on commit 255d19f

Please sign in to comment.