UNPKG

dp-contract-proxy-kit

Version:

Enable batched transactions and contract account interactions using a unique deterministic Gnosis Safe.

177 lines 8.47 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const constants_1 = require("../../utils/constants"); const transactions_1 = require("../../utils/transactions"); const TransactionManager_1 = require("../TransactionManager"); class CpkTransactionManager { /** * Returns the configuration of the CpkTransactionManager. * * @returns The name of the TransactionManager in use and the URL of the service */ get config() { return { name: TransactionManager_1.TransactionManagerNames.CpkTxManager, url: undefined }; } /** * Executes a list of transactions. * * @param options * @returns The transaction response */ execTransactions({ ownerAccount, safeExecTxParams, transactions, ethLibAdapter, contractManager, saltNonce, isDeployed, isConnectedToSafe, sendOptions }) { return __awaiter(this, void 0, void 0, function* () { const { contract: safeContract, proxyFactory, masterCopyAddress, fallbackHandlerAddress } = contractManager; if (!safeContract) { throw new Error('CPK Proxy contract uninitialized'); } if (isConnectedToSafe) { return this.execTxsWhileConnectedToSafe(ethLibAdapter, transactions, sendOptions); } // (r, s, v) where v is 1 means this signature is approved by the address encoded in value r // "Hashes are automatically approved by the sender of the message" const autoApprovedSignature = ethLibAdapter.abiEncodePacked({ type: 'uint256', value: ownerAccount }, // r { type: 'uint256', value: 0 }, // s { type: 'uint8', value: 1 } // v ); const txObj = isDeployed ? this.getSafeProxyTxObj(safeContract, safeExecTxParams, autoApprovedSignature) : this.getCPKFactoryTxObj(masterCopyAddress, fallbackHandlerAddress, safeExecTxParams, saltNonce, proxyFactory); const { success, gasLimit } = yield this.findGasLimit(ethLibAdapter, txObj, sendOptions); sendOptions.gas = gasLimit; const isSingleTx = transactions.length === 1; if (!success) { throw yield this.makeTransactionError(ethLibAdapter, safeExecTxParams, safeContract.address, gasLimit, isDeployed, isSingleTx); } const { contract, methodName, params } = txObj; return contract.send(methodName, params, sendOptions); }); } execTxsWhileConnectedToSafe(ethLibAdapter, transactions, sendOptions) { return __awaiter(this, void 0, void 0, function* () { if (transactions.some(({ operation }) => operation === transactions_1.OperationType.DelegateCall)) { throw new Error('DelegateCall unsupported by Gnosis Safe'); } if (transactions.length === 1) { const { to, value, data } = transactions[0]; return ethLibAdapter.ethSendTransaction(Object.assign({ to, value, data }, sendOptions)); } return { hash: yield ethLibAdapter.providerSend('gs_multi_send', transactions.map(({ to, value, data }) => ({ to, value, data }))) }; }); } getSafeProxyTxObj(safeContract, { to, value, data, operation }, safeAutoApprovedSignature) { return { contract: safeContract, methodName: 'execTransaction', params: [ to, value, data, operation, 0, 0, 0, constants_1.zeroAddress, constants_1.zeroAddress, safeAutoApprovedSignature ] }; } getCPKFactoryTxObj(masterCopyAddress, fallbackHandlerAddress, { to, value, data, operation }, saltNonce, proxyFactory) { return { contract: proxyFactory, methodName: 'createProxyAndExecTransaction', params: [masterCopyAddress, saltNonce, fallbackHandlerAddress, to, value, data, operation] }; } findGasLimit(ethLibAdapter, { contract, methodName, params }, sendOptions) { return __awaiter(this, void 0, void 0, function* () { function checkOptions(options) { return __awaiter(this, void 0, void 0, function* () { try { return yield contract.call(methodName, params, options); } catch (e) { return false; } }); } const toNumber = (num) => Number(num.toString()); if (!sendOptions.gas) { const blockGasLimit = toNumber((yield ethLibAdapter.getBlock('latest')).gasLimit); const gasEstimateOptions = Object.assign(Object.assign({}, sendOptions), { gas: blockGasLimit }); if (!(yield checkOptions(gasEstimateOptions))) { return { success: false, gasLimit: blockGasLimit }; } const gasSearchError = 10000; let gasLow = yield contract.estimateGas(methodName, params, sendOptions); let gasHigh = blockGasLimit; gasEstimateOptions.gas = gasLow; if (!(yield checkOptions(gasEstimateOptions))) { while (gasLow + gasSearchError <= gasHigh) { const testGasLimit = Math.floor((gasLow + gasHigh) * 0.5); gasEstimateOptions.gas = testGasLimit; if (yield checkOptions(gasEstimateOptions)) { // values > gasHigh will work gasHigh = testGasLimit - 1; } else { // values <= gasLow will fail gasLow = testGasLimit + 1; } } } else { gasHigh = gasLow - 1; } // the final target gas value is > gasHigh const gasLimit = Math.min(Math.ceil((gasHigh + 1) * 1.02), blockGasLimit); return { success: true, gasLimit }; } return { success: yield checkOptions(sendOptions), gasLimit: toNumber(sendOptions.gas) }; }); } makeTransactionError(ethLibAdapter, { to, value, data, operation }, safeAddress, gasLimit, isDeployed, isSingleTx) { return __awaiter(this, void 0, void 0, function* () { let errorMessage = `${isDeployed ? '' : 'proxy creation and '}${isSingleTx ? 'transaction' : 'batch transaction'} execution expected to fail`; let revertData, revertMessage; if (isSingleTx && operation === transactions_1.OperationType.Call) { try { revertData = yield ethLibAdapter.getCallRevertData({ from: safeAddress, to, value, data, gasLimit }, 'latest'); revertMessage = ethLibAdapter.decodeError(revertData); errorMessage = `${errorMessage}: ${revertMessage}`; } catch (e) { // empty } } return new transactions_1.TransactionError(errorMessage, revertData, revertMessage); }); } } exports.default = CpkTransactionManager; //# sourceMappingURL=index.js.map