UNPKG

@kiroboio/fct-core

Version:

Kirobo.io FCT Core library

232 lines 9.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllRequiredApprovals = getAllRequiredApprovals; const fct_plugins_1 = require("@kiroboio/fct-plugins"); const ethers_1 = require("ethers"); const helpers_1 = require("../../helpers"); const classes_1 = require("../classes"); const { getAddress, id } = ethers_1.utils; const transferFunctionSignature = id("transferFrom(address,address,uint256)").toLowerCase(); function getAllRequiredApprovals(FCT) { if (!FCT.chainId) { throw new Error("No chainId or provider has been set"); } const chainId = FCT.chainId; const calls = FCT.calls; const callsAsObjects = FCT.callsAsObjects; const requiredApprovalsMap = new Map(); const requiredApprovals = []; for (const [callIndex, call] of callsAsObjects.entries()) { if (typeof call.to !== "string") { continue; } const callClass = new classes_1.CallBase(call); let approvals = []; const funcitonSignature = callClass.getFunctionSignature(); // If the functionSignature is id("transferFrom(address,address,uint256)") // This is a bypass for an edge case where transferFrom is for ERC20 and ERC721 if (funcitonSignature.toLowerCase() === transferFunctionSignature) { handleTransferFrom(call, requiredApprovals); continue; } const mainCallData = calls[callIndex]; if (mainCallData.plugin) { const pluginApprovals = mainCallData.plugin.getRequiredApprovals(); approvals = pluginApprovals; } else { try { const pluginData = (0, fct_plugins_1.getPlugin)({ signature: funcitonSignature, address: call.to, chainId, }); if (pluginData && !Array.isArray(pluginData)) { approvals = getApprovalsFromPlugin({ pluginData, call, chainId }); } } catch (error) { continue; } } if (approvals.length > 0 && typeof call.from === "string") { const requiredApprovalsWithFrom = getApprovalsWithFrom({ approvals, callIndex, call, callsAsObjects, }); for (const approval of requiredApprovalsWithFrom) { if (approval.protocol === "ERC20") { const id = `${approval.from}-${approval.method}-${approval.token}-${approval.params.spender}`; if (requiredApprovalsMap.has(id)) { // Need to add amount to the existing approval const existingApproval = requiredApprovalsMap.get(id); existingApproval.params.amount = (BigInt(existingApproval.params.amount) + BigInt(approval.params.amount)).toString(); } else { requiredApprovalsMap.set(id, approval); } continue; } const regularId = `${approval.from}-${approval.method}-${approval.token}-${JSON.stringify(approval.params)}`; requiredApprovalsMap.set(regularId, approval); } } } return [...requiredApprovals, ...Array.from(requiredApprovalsMap.values())]; } function handleTransferFrom(call, requiredApprovals) { const whoNeedsToApprove = call.params ? call.params[0].value : ""; const amount = call.params ? call.params[2].value : ""; const from = call.from; if (typeof whoNeedsToApprove === "string" && typeof amount === "string" && typeof from === "string" && from.toLowerCase() !== whoNeedsToApprove.toLowerCase()) { requiredApprovals.push({ protocol: "ERC20", from: whoNeedsToApprove, method: "approve", token: call.to, params: { spender: from, amount: amount, }, }); } } function getApprovalsFromPlugin({ pluginData, call, chainId, }) { const initPlugin = new pluginData.plugin({ chainId, vaultAddress: typeof call.from === "string" ? call.from : "", }); if (pluginData.name === "MulticallPlugin") { function getMethodParamsForMulticallPlugins(params) { return params.map((param) => { if (param.type === "tuple[]") { return param.value.map((p) => getMethodParamsForMulticallPlugins(p)); } if (param.type === "tuple") { return getMethodParamsForMulticallPlugins(param.value); } return param.value; }); } const _methodParams = getMethodParamsForMulticallPlugins(call.params); initPlugin.setMethodParams(_methodParams); } else { const methodParams = call.params ? call.params.reduce((acc, param) => { acc[param.name] = param.value; return acc; }, {}) : {}; initPlugin.input.set({ to: call.to, methodParams, }); } return initPlugin.getRequiredApprovals(); } function getApprovalsWithFrom({ approvals, callIndex, call, callsAsObjects, }) { const mappedApprovals = approvals.map((approval) => handleApproval(approval, call)); console.log(mappedApprovals); return mappedApprovals.filter((approval) => { if (typeof call.from !== "string") { return true; } const isGoingToGetApproved = callsAsObjects.some((fctCall, i) => { if (i >= callIndex) return false; // If the call is after the current call, we don't need to check const { to, method, from } = fctCall; if (typeof to !== "string" || typeof from !== "string") return false; // If the call doesn't have a to or from, we don't need to check // Check if there is the same call inside FCT and BEFORE this call. If there is, we don't need to approve again return (to.toLowerCase() === approval.token.toLowerCase() && method === approval.method && from.toLowerCase() === approval.from.toLowerCase()); }); if (isGoingToGetApproved) return false; const caller = getAddress(call.from); // If the protocol is AAVE, we check if the caller is the spender and the approver if (approval.protocol === "AAVE") { const whoIsApproving = getAddress(approval.from); const whoIsSpending = getAddress(approval.params.delegatee); // If the caller is the spender and the approver - no need to approve return !(caller === whoIsSpending && caller === whoIsApproving); } // If the protocol is ERC721 and the call method is safeTransferFrom or transferFrom if (approval.protocol === "ERC721" && (call.method === "safeTransferFrom" || call.method === "transferFrom")) { const whoIsSending = getAddress(approval.from); const whoIsSpending = getAddress(approval.params.spender); // If the caller and spender is the same, no need to approve return whoIsSending !== whoIsSpending; } return true; }); } const manageValue = (value) => { if (helpers_1.InstanceOf.Variable(value) || !value) { return ""; } return value; }; function handleApproval(approval, call) { if (approval.method === "approve") { const data = { token: manageValue(approval.to), method: approval.method, from: manageValue((approval.from || call.from)), }; if (approval.protocol === "ERC20") { return { ...data, protocol: approval.protocol, params: { spender: manageValue(approval.params[0]), amount: approval.params[1], }, }; } else if (approval.protocol === "ERC721") { return { ...data, protocol: approval.protocol, params: { spender: manageValue(approval.params[0]), tokenId: approval.params[1], }, }; } } if (approval.method === "setApprovalForAll" && (approval.protocol === "ERC721" || approval.protocol === "ERC1155")) { return { protocol: approval.protocol, token: manageValue(approval.to), method: approval.method, params: { spender: manageValue(approval.params[0]), // Who is going to spend approved: approval.params[1] === "true", ids: approval.params[2], }, from: manageValue((approval.from || call.from)), // Who needs to approve }; } if (approval.protocol === "AAVE" && approval.method === "approveDelegation") { return { protocol: approval.protocol, token: manageValue(approval.to), method: approval.method, params: { delegatee: manageValue(approval.params[0]), // Who is going to spend amount: approval.params[1], }, from: manageValue((approval.from || call.from)), // Who needs to approve }; } throw new Error("Unknown method for plugin"); } //# sourceMappingURL=getAllRequiredApprovals.js.map