dp-contract-proxy-kit
Version:
Enable batched transactions and contract account interactions using a unique deterministic Gnosis Safe.
175 lines • 8.32 kB
JavaScript
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());
});
};
import { zeroAddress } from '../../utils/constants';
import { OperationType, TransactionError } from '../../utils/transactions';
import { TransactionManagerNames } from '../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: 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 === 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,
zeroAddress,
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 === 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 TransactionError(errorMessage, revertData, revertMessage);
});
}
}
export default CpkTransactionManager;
//# sourceMappingURL=index.js.map