Skip to content

Instantly share code, notes, and snippets.

@santteegt
Created September 11, 2021 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save santteegt/4d405cd4553f3201384c2e5edb2245f2 to your computer and use it in GitHub Desktop.
Save santteegt/4d405cd4553f3201384c2e5edb2245f2 to your computer and use it in GitHub Desktop.
Some methods I Implemented to make a Moloch minion interact with a Gnosis Safe. Deprecated but keeping this for posterity
// import EthersSafe, {
// SafeTransactionDataPartial,
// } from '@gnosis.pm/safe-core-sdk';
// import { OperationType } from '@gnosis.pm/safe-core-sdk-types';
import { ethers } from 'ethers';
import Web3 from 'web3';
// import { ecrecover, toBuffer } from 'ethereumjs-util';
import minionSafeModuleEnablerAbi from '../contracts/minionSafeModuleEnabler.json';
// import safeCreateAndAddModulesAbi from '../contracts/safeCreateAndAddModule.json';
import safeMasterCopyAbi from '../contracts/safeGnosis.json';
// import safeModuleDataWrapperAbi from '../contracts/safeModuleDataWrapper.json';
import safeMultisendAbi from '../contracts/safeMultisend.json';
import safeProxyFactoryAbi from '../contracts/safeProxyFactory.json';
// import minionAbi from '../contracts/minion.json';
import { chainByID } from '../utils/chain';
// import { postApiGnosis } from '../utils/requests';
export const MinionSafeService = ({ web3, chainID }) => {
if (!web3) {
const rpcUrl = chainByID(chainID).rpc_url;
web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl));
}
const networkConfig = chainByID(chainID);
// const { network } = networkConfig;
const safeConfig = networkConfig.gnosis_safe;
const safeProxyFactoryAddress = safeConfig.proxy_factory;
const createAndAddModulesAddress = safeConfig.create_and_add_modules;
const safeMasterCopyAddress = safeConfig.master_copy;
const multiSendAddress = safeConfig.multisend;
const moduleEnablerAddress = safeConfig.module_enabler;
// contract for deploying new safes
const safeProxyFactory = new web3.eth.Contract(
safeProxyFactoryAbi,
safeProxyFactoryAddress,
);
// contract for deploying safe modules
// const safeCreateAndAddModulesContract = new web3.eth.Contract(
// safeCreateAndAddModulesAbi,
// createAndAddModulesAddress,
// );
// contract for the safe
// const safe = new web3.eth.Contract(safeMasterCopyAbi, safeMasterCopyAddress);
// console.log('WEB3 ==>', web3);
// const provider = new ethers.providers.Web3Provider(web3);
// const signer = provider.getSigner(0); // connected wallet
// console.log('SIGNER', signer);
return service => {
// // Load a Safe using the Gnosis Core SDK
// if (service === 'getSafe') {
// return async ({ safeAddress, shouldExist }) => {
// try {
// const provider = new ethers.providers.Web3Provider(
// web3.currentProvider,
// );
// const providerOrSigner = provider.getSigner();
// const currentProviderOrSigner =
// providerOrSigner || ethers.getDefaultProvider();
// return await EthersSafe.create({
// ethers,
// safeAddress,
// providerOrSigner,
// });
// } catch (error) {
// if (shouldExist) {
// throw new Error(error);
// }
// }
// return null;
// };
// }
// common entrypoint for contract view methods
if (service === 'call') {
return async ({ safeAddress, method, args }) => {
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeAddress,
);
const rs = await safeContract.methods[method](...args).call();
console.log('Contract call RS', rs);
return rs;
};
}
// if (service === 'getMinionPrevalidatedSignature') {
// return ({ minionAddress, txHash }) => {
// const r = minionAddress
// .slice(2)
// .padStart(64, 0)
// .toLowerCase();
// const s = txHash || ''.padStart(64, 0);
// const v = '01';
// return `0x${r}${s}${v}`;
// };
// }
// if (service === 'addDelegate') {
// return async ({
// safe,
// delegate,
// delegator,
// label = 'New Delegate from MinionSafe',
// }) => {
// console.log('AddDelegate', safe, delegate, label);
// try {
// const topt = Math.floor(new Date().getTime() / 1000 / 3600);
// const toSign =
// web3.utils.toChecksumAddress(delegate) + topt.toString();
// console.log('toSign', delegate, topt.toString(), toSign);
// const hasCode = await web3.eth.getCode(delegator);
// console.log('hasCode', hasCode);
// if (hasCode !== '0x') {
// // Call approveHash (or EIP1271)
// console.log('call approveHash');
// const dataHash = ethers.utils.id(toSign);
// // const r = delegator
// // .slice(2)
// // .padStart(64, 0);
// // const s = ''.padStart(32, 0);
// // // const s = ethers.utils.id(toSign).slice(2);
// // // const s = web3.utils.sha3(toSign).slice(2);
// // const v = '01'; // Pre-Validated Signature
// // console.log('RSV', r, r.length, s, s.length, v, v.length);
// // const signature = `0x${r}${s}${v}`;
// // console.log(
// // 'signature',
// // signature,
// // signature.length,
// // web3.utils.isHex(signature),
// // );
// localStorage.setItem(
// `dataHash_${safe}_${delegator}_${delegate}`.toLowerCase(),
// dataHash,
// );
// const safeContract = new web3.eth.Contract(safeMasterCopyAbi, safe);
// const hexData = safeContract.methods
// .approveHash(dataHash) // Pre-Validated Signature
// .encodeABI();
// console.log('hexData', hexData);
// return {
// minionProposal: true,
// data: hexData,
// };
// }
// // Use Gnosis Tx Service with EOA account
// const hashToSign = web3.utils.sha3(
// `\x19Ethereum Signed Message:\n${toSign.length}${toSign}`,
// );
// const signature = await web3.eth.personal.sign(
// hashToSign,
// web3.utils.toChecksumAddress(delegator),
// );
// const r = signature.slice(0, 66);
// const s = signature.slice(66, 130);
// // eth_sign signature -> signature_type > 30 -> v = v + 4
// const v = (parseInt(signature.slice(130, 132), 16) + 4).toString(16);
// // TODO: document and remove this
// const recover = web3.eth.accounts.recover(
// hashToSign,
// // with eth.persona.sign
// signature,
// false,
// // with eth.sign
// // signature0,
// // true,
// );
// console.log('ecrecover', recover);
// const payload = {
// delegate,
// signature: r + s + v,
// label,
// };
// const rs = await postApiGnosis(
// network,
// `safes/${safe}/delegates/`,
// payload,
// );
// console.log('RS', rs);
// if (rs.statusCode >= 400) {
// throw new Error(rs.data?.nonFieldErrors);
// }
// return {
// minionProposal: false,
// data: rs.data,
// };
// } catch (error) {
// console.error('An error occurred while trying to addDelegate', error);
// }
// return null;
// };
// }
if (service === 'enableMinionModule') {
return ({ minionAddress }) => {
if (!moduleEnablerAddress) {
throw new Error('Minion Safe not supported in current network.');
}
const moduleEnabler = new web3.eth.Contract(
minionSafeModuleEnablerAbi,
moduleEnablerAddress,
);
return moduleEnabler.methods.enableModule(minionAddress).encodeABI();
};
}
// useful when a module was already deployed and setup
if (service === 'enableModule') {
return async ({ moduleAddress }) => {
try {
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeMasterCopyAddress,
);
const enableModuleData = safeContract.methods
.enableModule(moduleAddress)
// .enableModule('0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134')
.encodeABI();
return enableModuleData;
} catch (err) {
console.log(err);
}
return null;
};
}
if (service === 'swapOwner') {
return ({ safeAddress, args }) => {
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeAddress,
);
return safeContract.methods.swapOwner(...args).encodeABI();
};
}
// if (service === 'sdkEnableModule') {
// return async ({ safeSdk, moduleAddress }) => {
// try {
// const safeTransaction = await safeSdk.getEnableModuleTx(
// moduleAddress,
// // '0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134',
// );
// return safeTransaction;
// } catch (err) {
// console.log(err);
// }
// return null;
// };
// }
// TODO: include extra modules
// extraModules: [ { } ]
if (service === 'setupSafeModuleData') {
return async ({
signers,
threshold,
enableModuleAddress,
enableModuleData,
// extraModules = [],
}) => {
console.log(
'setupModuleData',
signers,
threshold,
enableModuleAddress,
enableModuleData,
);
try {
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeMasterCopyAddress,
);
// TODO: additional modules e.g. AMB module
// if (extraModules) {
// console.log('TODO: Set Extra modules');
// // const moduleDataWrapper = new web3.eth.Contract(safeModuleDataWrapperAbi);
// // const moduleData = []; // TODO: ABI encoded data for each module
// // // Remove method id (10) and position of data in payload (64)
// // const modulesCreationData = moduleData.reduce(
// // (acc, data) =>
// // acc +
// // moduleDataWrapper.methods
// // .setup(data)
// // .encodeABI()
// // .substr(74),
// // '0x',
// // );
// }
const addModulesContract =
enableModuleAddress || createAndAddModulesAddress;
// console.log(
// 'SEtUP',
// signers, // owners
// threshold, // signatures threshold
// enableModuleData === '0x'
// ? ethers.constants.AddressZero
// : addModulesContract, // to - Contract address for optional delegate call.
// enableModuleData, // data - Data payload for optional delegate call.
// ethers.constants.AddressZero, // fallbackHandler
// ethers.constants.AddressZero, // paymentToken
// 0, // payment
// ethers.constants.AddressZero, // paymentReceiver
// );
return safeContract.methods
.setup(
signers, // owners
threshold, // signatures threshold
enableModuleData === '0x'
? ethers.constants.AddressZero
: addModulesContract, // to - Contract address for optional delegate call.
enableModuleData, // data - Data payload for optional delegate call.
ethers.constants.AddressZero, // fallbackHandler
ethers.constants.AddressZero, // paymentToken
0, // payment
ethers.constants.AddressZero, // paymentReceiver
)
.encodeABI();
} catch (err) {
console.log(err);
}
return null;
};
}
if (service === 'calculateProxyAddress') {
return async ({ saltNonce, setupData }) => {
try {
const proxyCreationCode = await safeProxyFactory.methods
.proxyCreationCode()
.call();
const initCode = ethers.utils.solidityPack(
['bytes', 'uint256'],
[proxyCreationCode, safeMasterCopyAddress],
);
const salt = ethers.utils.solidityKeccak256(
['bytes32', 'uint256'],
[ethers.utils.solidityKeccak256(['bytes'], [setupData]), saltNonce],
);
return ethers.utils.getCreate2Address(
safeProxyFactory.options.address,
salt,
ethers.utils.keccak256(initCode),
);
} catch (error) {
console.error('error', error);
}
return null;
};
}
// args: [ mastercopy, setupData, saltNonce ]
if (service === 'createProxyWithNonce') {
return async ({ args, address, poll, onTxHash }) => {
try {
const tx = await safeProxyFactory.methods[service](...args);
return tx
.send({ from: address })
.on('transactionHash', txHash => {
if (poll) {
onTxHash();
poll(txHash);
}
})
.on('error', error => {
console.error(error);
});
} catch (error) {
console.error('error', error);
return error;
}
};
}
if (service === 'approveHash') {
return ({ safeAddress, args }) => {
const safe = new web3.eth.Contract(safeMasterCopyAbi, safeAddress);
return safe.methods.approveHash(...args).encodeABI();
};
}
if (service === 'isHashApproved') {
return async ({ safeAddress, args }) => {
const safe = new web3.eth.Contract(safeMasterCopyAbi, safeAddress);
return safe.methods.approvedHashes(...args).call();
};
}
if (service === 'getTransactionHash') {
return async ({ safeAddress, args }) => {
const safe = new web3.eth.Contract(safeMasterCopyAbi, safeAddress);
return safe.methods.getTransactionHash(...args).call();
};
}
if (service === 'execTransactionFromModule') {
return ({ to, value, data, operation }) => {
/**
* Encodes a transaction from the Gnosis API into a module transaction
* @returns ABI encoded function call to `execTransactionFromModule`
*/
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeMasterCopyAddress,
);
return safeContract.methods
.execTransactionFromModule(
to,
value,
data !== null ? data : '0x',
operation,
)
.encodeABI();
};
}
if (service === 'multisendCall') {
// txList -> Array<Tx>
// Tx -> { to, vakue, data, operation }
return ({ txList }) => {
const encodedTxData = `0x
${txList
.map(tx => {
const data = ethers.utils.arrayify(tx.data);
const encoded = ethers.utils.solidityPack(
['uint8', 'address', 'uint256', 'uint256', 'bytes'],
[tx.operation, tx.to, tx.value, data.length, data],
);
return encoded.slice(2);
})
.join('')}`;
const multisendContract = new web3.eth.Contract(
safeMultisendAbi,
multiSendAddress,
);
// const multisendTx = {
// to: multisendContract.address,
// value: '0',
// data:
// multisendContract.methods.multiSend(encodedTxData).encodeABI(),
// operation: OperationType.DelegateCall,
// }
// console.log('Multisend TX', encodedTxData, multisendTx);
return multisendContract.methods.multiSend(encodedTxData).encodeABI();
};
}
if (service === 'execTransaction') {
return ({
to,
value,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
signatures,
}) => {
/**
* Encodes a transaction from the Gnosis API into a multisig transaction
* @returns ABI encoded function call to `execTransaction`
*/
const safeContract = new web3.eth.Contract(
safeMasterCopyAbi,
safeMasterCopyAddress,
);
return safeContract.methods
.execTransaction(
to,
value,
data !== null ? data : '0x',
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
signatures,
)
.encodeABI();
};
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment