Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
851d4ef487 | ||
|
77958b3909 |
@ -1,3 +1,10 @@
|
||||
# Deployments
|
||||
|
||||
- `MinimalInstanceFactory`: [0x9d00007c0f5037157b5be8bff174b194a99118d0](https://etherscan.io/address/0x9d00007c0f5037157b5be8bff174b194a99118d0)
|
||||
- `InstanceProposalFactory`: [0x10715a092e160793C278a9830F0f8D4417B94D71](https://etherscan.io/address/0x10715a092e160793C278a9830F0f8D4417B94D71)
|
||||
- `ERC20TornadoCloneable (impl contract)`: [0xED2c9A637379DBF045982335db34Ea948F5FDB10](https://etherscan.io/address/0xED2c9A637379DBF045982335db34Ea948F5FDB10)
|
||||
- `ETHTornadoCloneable (impl contract)`: [0xFDE7d58A869B7D4B11cA57A35a10D3D95B1683B2](https://etherscan.io/address/0xFDE7d58A869B7D4B11cA57A35a10D3D95B1683B2)
|
||||
|
||||
# Reconstruction of `tornado-instances`
|
||||
|
||||
This is a (NOT-MINIMAL) reconstruction of the Tornado Cash `tornado-instances` repository with additional contract variants added. It is not minimal because some dependencies, because of the Github takedowns (and other stuff), had annoying build issues (at least for me) which made me inline a lot of the contract code.
|
||||
|
@ -21,4 +21,8 @@ module.exports = {
|
||||
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
|
||||
TWAPSlotsMin: 80,
|
||||
deployments: {
|
||||
minimalFactory: '0x9d00007c0f5037157b5be8bff174b194a99118d0',
|
||||
proposalFactory: '0x10715a092e160793C278a9830F0f8D4417B94D71',
|
||||
},
|
||||
}
|
||||
|
98
fpost.md
Normal file
98
fpost.md
Normal file
@ -0,0 +1,98 @@
|
||||
# You can now deploy proposals for instances & instances yourself! 🥳
|
||||
|
||||
Hi guys! 👋
|
||||
|
||||
Tornado Cash wouldn't be anything without the on-chain instances, but up until now there was the problem that it wasn't easy for anyone to deploy instances and propose them. For this reason, two new contracts have been deployed to Mainnet and verified. These are the (both links link to Etherscan!) [`MinimalInstanceFactory`](https://etherscan.io/address/0x9d00007c0f5037157b5be8bff174b194a99118d0) and [`InstanceProposalFactory`](https://etherscan.io/address/0x10715a092e160793C278a9830F0f8D4417B94D71) together with two implementation contracts, namely [`ETHTornadoCloneable`](https://etherscan.io/address/0xED2c9A637379DBF045982335db34Ea948F5FDB10) and [`ERC20TornadoCloneable`](https://etherscan.io/address/0xFDE7d58A869B7D4B11cA57A35a10D3D95B1683B2).
|
||||
|
||||
I'll call the "MinimalInstanceFactory" the "Clone factory 🏭" and the "InstanceProposalFactory" the "Proposal factory 🏭" going further!
|
||||
|
||||
Essentially, now you will be able to deploy proposals (and new instances) by yourself, starting via manual contract calls via Etherscan (or other methods, we will discuss this later) until UI's and other stuff implement methods for this to be done from other interfaces as well!
|
||||
|
||||
Addresses:
|
||||
|
||||
```
|
||||
MinimalInstanceFactory = 0x9d00007c0f5037157b5be8bff174b194a99118d0
|
||||
InstanceProposalFactory = 0x10715a092e160793C278a9830F0f8D4417B94D71
|
||||
ERC20TornadoCloneable (implementation) = 0xED2c9A637379DBF045982335db34Ea948F5FDB10
|
||||
ETHTornadoCloneable (implementation) = 0xFDE7d58A869B7D4B11cA57A35a10D3D95B1683B2
|
||||
```
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Obviously you should not just believe me but also check out the code of the contracts out by yourself. For example, you could take the older contracts and put them into a file, then compare them with the ones from the Github `tornado-core` and other repositories. Do note that the relative paths of the import statements might be different in some contract files, but comb through the code and you will see it is exactly the same logic, other than the stuff that was added on top to make things work (the most important part security wise, you can imagine, is that the cloneable instances are safe).
|
||||
|
||||
## How it works
|
||||
|
||||
The Instance factory 🏭 allows you to create an instance for any token of any denomination, you just have to call the `createInstanceClone(uint256 _denomination, address _token)` function with a wanted denomination and a token address, and voila - the instance will be created, no strings attached! Note - a new instance won't be deployed if there already is one for some token and denomination, it will just return the address.
|
||||
|
||||
This created instance is immediately ready for depositing, the source code on Etherscan will refer to the _implementation_ (because these are cloned contracts) source code. Let's continue on to the Proposal factory, which is where things get spicy.
|
||||
|
||||
The Proposal factory 🏭 deploys _proposals_ to add Instances to the TORN staking system. The proposed instances can be any token or denomination, but with a catch - since the staking system uses Uniswap V3 to get some TWAP (Time Weighted Average Price) data, we need a Uniswap V3 pool with a reliable history price history. This means, that the Proposal factory 🏭 will not consider every token to be eligible to be _added to the staking system._ It automatically checks this.
|
||||
|
||||
Otherwise, the Proposal factory 🏭 allows you to create a proposal for any amount of tokens and denominations as long as it fits within a single block! You just have to call one function (`createProposalContract`, more on it later), take the address, and propose that address as a new proposal, done - your instance are proposed!
|
||||
|
||||
## Details
|
||||
|
||||
Now for some necessary details when it comes to deploying the proposals, because this can get just a little bit complicated! 😬
|
||||
|
||||
In future, future-ish, soon (I don't know!) this should be handled automatically (and not via 🐸 REEEEtherscan), but for now start noting down! ✍️
|
||||
|
||||
The most complicated part is choosing the denominations wisely!
|
||||
|
||||
---
|
||||
|
||||
The denominations MUST be in the following format:
|
||||
|
||||
`p = power, d = denomination, d * 10^p where 1 <= d <= 1099511627775 && 0 <= p <= 65535`
|
||||
|
||||
`(2^16 - 1 === 65535 , 2^40 - 1 === 1099511627775)`
|
||||
|
||||
This is because we are storing the data efficiently to reduce gas costs! In MANY CASES:
|
||||
|
||||
`p = 18`
|
||||
|
||||
Because `2^40 - 1` will be enough to represent how large the denomination is, if not, bump `p`!
|
||||
|
||||
For each token, there will be ONE SET of denominations and ONE power.
|
||||
|
||||
---
|
||||
|
||||
The rest of the construction goes as follows:
|
||||
|
||||
1. Choose the tokens and for each one a set of denominations and one power.
|
||||
2. For each token, choose a UNISWAP POOL SWAPPING FEE (the usual ones: `10000 == 1%, 3000 == 0.3%, 500 = 0.05%`).
|
||||
3. For each DENOMINATION choose a PROTOCOL FEE (fee on withdraw, `100 == 1%, max 3%`).
|
||||
4. Count the total number of denominations for all tokens.
|
||||
|
||||
YOU ARE ALMOST DONE.
|
||||
|
||||
Filling in the function goes as follows:
|
||||
|
||||
```
|
||||
createProposalContract(
|
||||
[token_address_1, token_address_2, ..., token_address_t],
|
||||
[swapping_fee_1, swapping_fee_2, ..., swapping_fee_t],
|
||||
[power_1, power_2, ..., power_t],
|
||||
[[denomination_1_1, ..., denomination_1_d], ..., [..., denomination_t_d]],
|
||||
[[fee_1_1, ..., fee_1_d], ..., [..., fee_t_d]],
|
||||
d (the number of denominations we used to index above)
|
||||
)
|
||||
```
|
||||
|
||||
YOU ARE DONE!
|
||||
|
||||
---
|
||||
|
||||
A mini note on the Instance factory 🏭:
|
||||
|
||||
The above doesn't count for deploying instances, when using the `createInstanceClone` function, you just use the full denomination number (with decimals and all of that) and an address.
|
||||
|
||||
---
|
||||
|
||||
Obviously you can use Tor Browser to access Etherscan and connect with say... a Metamask hot wallet (because it doesn't recognize cold wallets because it blocks access to your devices, but you would obviously use a hot wallet), and then just call the function from there.
|
||||
|
||||
Furthermore if you're a dev for now grab the ABI off Etherscan or encode according to the function signature, this should be easy since there is no bytes data involved.
|
||||
|
||||
---
|
||||
|
||||
Why, THANK YOU ~ VERY MUCH ~ for reading this through! 👋
|
@ -27,10 +27,8 @@ const prompter = createInterface({ input: process.stdin, output: process.stdout
|
||||
function _prompt(prompt, resolve) {
|
||||
prompter.question(prompt, (answer) => {
|
||||
if (answer == 'y') {
|
||||
userInput.close()
|
||||
resolve(true)
|
||||
} else if (answer == 'n') {
|
||||
userInput.close()
|
||||
resolve(false)
|
||||
} else _prompt('', resolve)
|
||||
})
|
||||
@ -48,34 +46,42 @@ function happyVerifiedMessage(name, address) {
|
||||
return `\n${name} @ ${address} successfully verified on Etherscan! 🥳\n`
|
||||
}
|
||||
|
||||
function timeout(seconds) {
|
||||
return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
const promptMessageBase = (middle) => `\n${middle}\n\nAre you sure you would like to continue? 🧐 (y/n): `
|
||||
|
||||
async function main() {
|
||||
const minimalFactoryContractFactory = await ethers.getContractFactory('MinimalInstanceFactory')
|
||||
const proposalFactoryContractFactory = await ethers.getContractFactory('InstanceProposalFactory')
|
||||
const minimalFactoryFactory = await ethers.getContractFactory('MinimalInstanceFactory')
|
||||
const proposalFactoryFactory = await ethers.getContractFactory('InstanceProposalFactory')
|
||||
const chainId = (await ethers.provider.getNetwork()).chainId
|
||||
|
||||
const signer = await ethers.getSigner()
|
||||
|
||||
let minimalFactory, proposalFactory, nativeCloneableImplAddr, erc20CloneableImplAddr
|
||||
|
||||
if (await prompt(promptMessageBase('Continuing to MinimalInstanceFactory deployment.'))) {
|
||||
minimalFactory = await minimalFactoryContractFactory.deploy(
|
||||
minimalFactory = await minimalFactoryFactory.deploy(
|
||||
config.verifier,
|
||||
config.hasher,
|
||||
config.merkleTreeHeight,
|
||||
)
|
||||
console.log(happyDeployedMessage('MinimalInstanceFactory', chainId, minimalFactory.address))
|
||||
} else {
|
||||
return '\nDecided to stop at InstanceProposalFactory deployment.\n'
|
||||
return '\nDecided to stop at MinimalInstanceFactory deployment.\n'
|
||||
}
|
||||
|
||||
console.log(happyDeployedMessage('MinimalInstanceFactory', minimalFactory.address))
|
||||
console.log(happyDeployedMessage('MinimalInstanceFactory', chainId, minimalFactory.address))
|
||||
|
||||
nativeCloneableImplAddr = await minimalFactory.nativeCurImpl()
|
||||
erc20CloneableImplAddr = await minimalFactory.ERC20Impl()
|
||||
|
||||
console.log(happyDeployedMessage('ETHTornadoCloneable', nativeCloneableImplAddr))
|
||||
console.log(happyDeployedMessage('ERC20TornadoCloneable', erc20CloneableImplAddr))
|
||||
console.log(happyDeployedMessage('ETHTornadoCloneable', chainId, nativeCloneableImplAddr))
|
||||
console.log(happyDeployedMessage('ERC20TornadoCloneable', chainId, erc20CloneableImplAddr))
|
||||
|
||||
if (await prompt(promptMessageBase('Continuing to InstanceProposalFactory deployment.'))) {
|
||||
proposalFactory = await proposalFactoryContractFactory.deploy(
|
||||
proposalFactory = await proposalFactoryFactory.deploy(
|
||||
config.governance,
|
||||
minimalFactory.address,
|
||||
config.instanceRegistry,
|
||||
@ -84,7 +90,7 @@ async function main() {
|
||||
config.TWAPSlotsMin,
|
||||
)
|
||||
|
||||
console.log(happyDeployedMessage('InstanceProposalFactory', proposalFactory.address))
|
||||
console.log(happyDeployedMessage('InstanceProposalFactory', chainId, proposalFactory.address))
|
||||
} else {
|
||||
return '\nDecided to stop at InstanceProposalFactory deployment.\n'
|
||||
}
|
||||
@ -96,6 +102,8 @@ async function main() {
|
||||
})
|
||||
|
||||
console.log(happyVerifiedMessage('MinimalInstanceFactory', minimalFactory.address))
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: proposalFactory.address,
|
||||
@ -110,6 +118,8 @@ async function main() {
|
||||
})
|
||||
|
||||
console.log(happyVerifiedMessage('InstanceProposalFactory', proposalFactory.address))
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: nativeCloneableImplAddr,
|
||||
@ -117,6 +127,8 @@ async function main() {
|
||||
})
|
||||
|
||||
console.log(happyVerifiedMessage('ETHTornadoCloneable', nativeCloneableImplAddr))
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: erc20CloneableImplAddr,
|
||||
|
@ -1,32 +0,0 @@
|
||||
const { ethers } = require('hardhat')
|
||||
const config = require('../config')
|
||||
const { generate } = require('../src/generateAddresses')
|
||||
|
||||
async function deploy({ address, bytecode, singletonFactory }) {
|
||||
const contractCode = await ethers.provider.getCode(address)
|
||||
if (contractCode !== '0x') {
|
||||
console.log(`Contract ${address} already deployed. Skipping...`)
|
||||
return
|
||||
}
|
||||
await singletonFactory.deploy(bytecode, config.salt, { gasLimit: config.deployGasLimit })
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||
const contracts = await generate()
|
||||
await deploy({ ...contracts.factory.implementation, singletonFactory })
|
||||
console.log(`Instance factory contract have been deployed on ${contracts.factory.implementation.address}`)
|
||||
await deploy({ ...contracts.factory.proxy, singletonFactory })
|
||||
console.log(`Instance factory proxy contract have been deployed on ${contracts.factory.proxy.address}`)
|
||||
await deploy({ ...contracts.proposalCreator.implementation, singletonFactory })
|
||||
console.log(`Proposal creator have been deployed on ${contracts.proposalCreator.implementation.address}`)
|
||||
await deploy({ ...contracts.proposalCreator.proxy, singletonFactory })
|
||||
console.log(`Proposal creator proxy have been deployed on ${contracts.proposalCreator.proxy.address}`)
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
@ -1,32 +0,0 @@
|
||||
const { ethers } = require('hardhat')
|
||||
const config = require('../config')
|
||||
const { generate } = require('../src/generateAddresses')
|
||||
|
||||
async function deploy({ address, bytecode, singletonFactory }) {
|
||||
const contractCode = await ethers.provider.getCode(address)
|
||||
if (contractCode !== '0x') {
|
||||
console.log(`Contract ${address} already deployed. Skipping...`)
|
||||
return
|
||||
}
|
||||
await singletonFactory.deploy(bytecode, config.salt, { gasLimit: config.deployGasLimit })
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||
const contracts = await generate()
|
||||
await deploy({ ...contracts.sidechainFactory.implementation, singletonFactory })
|
||||
await deploy({ ...contracts.sidechainFactory.proxy, singletonFactory })
|
||||
console.log(
|
||||
`SidechainInstanceFactory contract have been deployed on ${contracts.sidechainFactory.implementation.address} address`,
|
||||
)
|
||||
console.log(
|
||||
`SidechainInstanceFactory proxy contract have been deployed on ${contracts.sidechainFactory.proxy.address} address`,
|
||||
)
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
93
scripts/verify.js
Normal file
93
scripts/verify.js
Normal file
@ -0,0 +1,93 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const hre = require('hardhat')
|
||||
const config = require('../config')
|
||||
const { createInterface } = require('readline')
|
||||
|
||||
const prompter = createInterface({ input: process.stdin, output: process.stdout })
|
||||
|
||||
function _prompt(promptMessage, predicate, resolve) {
|
||||
prompter.question(promptMessage, (answer) => {
|
||||
if (predicate(answer)) {
|
||||
resolve(answer)
|
||||
} else {
|
||||
_prompt('', predicate, resolve)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addressPromptMessage = (contractName) =>
|
||||
`\n\nPlease enter the deploy address of the ${contractName} contract (CTRL-D/C to exit): `
|
||||
|
||||
function addressPrompt(name) {
|
||||
return new Promise((resolve) =>
|
||||
_prompt(addressPromptMessage(name), (answer) => answer.length === 42 || answer === 'skip', resolve),
|
||||
)
|
||||
}
|
||||
|
||||
function happyVerifiedMessage(name, address) {
|
||||
return `\n${name} @ ${address} successfully verified on Etherscan! 🥳\n`
|
||||
}
|
||||
|
||||
function timeout(seconds) {
|
||||
return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let minimalFactoryAddr = await addressPrompt('MinimalInstanceFactory')
|
||||
let proposalFactoryAddr = await addressPrompt('InstanceProposalFactory')
|
||||
let nativeCloneableImplAddr = await addressPrompt('ETH Instance Clone')
|
||||
let erc20CloneableImplAddr = await addressPrompt('Token Instance Clone')
|
||||
|
||||
if (minimalFactoryAddr !== 'skip') {
|
||||
await hre.run('verify:verify', {
|
||||
address: minimalFactoryAddr,
|
||||
constructorArguments: [config.verifier, config.hasher, config.merkleTreeHeight],
|
||||
})
|
||||
console.log(happyVerifiedMessage('MinimalInstanceFactory', minimalFactoryAddr))
|
||||
}
|
||||
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
if (proposalFactoryAddr !== 'skip') {
|
||||
await hre.run('verify:verify', {
|
||||
address: proposalFactoryAddr,
|
||||
constructorArguments: [
|
||||
config.governance,
|
||||
minimalFactoryAddr,
|
||||
config.instanceRegistry,
|
||||
config.UniswapV3Factory,
|
||||
config.WETH,
|
||||
config.TWAPSlotsMin,
|
||||
],
|
||||
})
|
||||
console.log(happyVerifiedMessage('InstanceProposalFactory', proposalFactoryAddr))
|
||||
}
|
||||
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
if (nativeCloneableImplAddr !== 'skip') {
|
||||
await hre.run('verify:verify', {
|
||||
address: nativeCloneableImplAddr,
|
||||
constructorArguments: [config.verifier, config.hasher],
|
||||
})
|
||||
console.log(happyVerifiedMessage('ETHTornadoCloneable', nativeCloneableImplAddr))
|
||||
}
|
||||
|
||||
console.log('\nWaiting 5 seconds.\n')
|
||||
await timeout(5)
|
||||
|
||||
if (erc20CloneableImplAddr !== 'skip') {
|
||||
await hre.run('verify:verify', {
|
||||
address: erc20CloneableImplAddr,
|
||||
constructorArguments: [config.verifier, config.hasher],
|
||||
})
|
||||
console.log(happyVerifiedMessage('ERC20TornadoCloneable', erc20CloneableImplAddr))
|
||||
}
|
||||
}
|
||||
|
||||
main().then((res) => {
|
||||
console.log(res ?? '\nScript succesfully finished.\n')
|
||||
})
|
Loading…
Reference in New Issue
Block a user