Created
September 11, 2021 02:26
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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