Complete deployment script

This commit is contained in:
Tornado Contrib 2024-03-30 06:10:48 +00:00
parent 36798e04f2
commit a2e8e5aa80
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
15 changed files with 587 additions and 37 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@ node_modules
# solidity-coverage files
/coverage
/coverage.json
/flatten

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Echoer {
event Echo(address indexed who, bytes data);
function echo(bytes calldata _data) external {
emit Echo(msg.sender, _data);
}
}

View File

@ -16,10 +16,11 @@ import { IVerifier, IHasher, ERC20Tornado } from "./ERC20Tornado.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
contract cTornado is ERC20Tornado {
address public immutable governance = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address public immutable governance;
IERC20 public immutable comp;
constructor(
address _governance,
IERC20 _comp,
IVerifier _verifier,
IHasher _hasher,
@ -28,6 +29,7 @@ contract cTornado is ERC20Tornado {
IERC20 _token
) ERC20Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight, _token) {
require(address(_comp) != address(0), "Invalid COMP token address");
governance = _governance;
comp = _comp;
}

View File

@ -5,10 +5,7 @@ pragma experimental ABIEncoderV2;
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { ITornadoInstance } from "./interfaces/ITornadoInstance.sol";
import { InstanceRegistry } from "./InstanceRegistry.sol";
import { IERC20, InstanceRegistry, ITornadoInstance } from "./InstanceRegistry.sol";
/// @dev contract which calculates the fee for each pool
contract FeeManager {

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @notice this contract should store ether for gas compensations and also retrieve the basefee
* @dev modified for non constant governance address and simpler code
* */
contract GasCompensationVault {
address public immutable GovernanceAddress;
modifier onlyGovernance() {
require(msg.sender == GovernanceAddress, "only gov");
_;
}
constructor(address _governance) {
GovernanceAddress = _governance;
}
/**
* @notice function to compensate gas by sending amount eth to a recipient
* @param recipient address to receive amount eth
* @param gasAmount the amount of gas to be compensated
* */
function compensateGas(address recipient, uint256 gasAmount) external onlyGovernance {
uint256 vaultBalance = address(this).balance;
uint256 toCompensate = gasAmount * block.basefee;
if (vaultBalance == 0) return;
//payable(recipient).send((toCompensate > vaultBalance) ? vaultBalance : toCompensate);
(bool success, ) = recipient.call{ value: (toCompensate > vaultBalance) ? vaultBalance : toCompensate }('');
require(success, "compensate gas failed");
}
/**
* @notice function to withdraw compensate eth back to governance
* @param amount the amount of eth to withdraw back to governance
* */
function withdrawToGovernance(uint256 amount) external onlyGovernance {
uint256 vaultBalance = address(this).balance;
//require(GovernanceAddress.sendEther((amount > vaultBalance) ? vaultBalance : amount), "pay fail");
(bool success, ) = GovernanceAddress.call{ value: (amount > vaultBalance) ? vaultBalance : amount }('');
require(success, "pay fail");
}
/**
* @notice receive ether function, does nothing but receive ether
* */
receive() external payable {}
}

View File

@ -7,7 +7,6 @@ import { Initializable } from "@openzeppelin/contracts-v3/proxy/Initializable.so
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { ITornadoInstance } from "./interfaces/ITornadoInstance.sol";
import { FeeManager } from "./FeeManager.sol";
interface ITornadoRouter {
function approveExactToken(IERC20 _token, address _spender, uint256 _amount) external;

View File

@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { OwnableLibrary } from "../libraries/OwnableLibrary.sol";
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts-v3/proxy/TransparentUpgradeableProxy.sol";
/**
* @dev Enables testnet contracts to be upgraded by the governance and maintainer, since we don't want to waste time
*/
contract TestnetAdminProxy is TransparentUpgradeableProxy {
modifier onlyOwner {
require(OwnableLibrary.getOwner() == msg.sender, "Not an owner");
_;
}
constructor(
address _logic,
address _admin,
bytes memory _data
) public payable TransparentUpgradeableProxy(_logic, _admin, _data) {
OwnableLibrary.setOwner(msg.sender);
}
function getCurrentOwner() external view returns (address) {
return OwnableLibrary.getOwner();
}
function changeOwner(address newOwner) external onlyOwner {
OwnableLibrary.setOwner(newOwner);
}
function upgradeToOwner(address newImplementation) external onlyOwner {
_upgradeTo(newImplementation);
}
function callToOwner(address target, bytes memory data) external payable onlyOwner {
(bool success, bytes memory returnData) = target.call{ value: msg.value }(data);
if (!success) {
assembly {
revert(add(32, returnData), mload(returnData))
}
}
}
function delegateToOwner(address target, bytes memory data) external payable onlyOwner {
(bool success, bytes memory returnData) = target.delegatecall(data);
if (!success) {
assembly {
revert(add(32, returnData), mload(returnData))
}
}
}
}

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { OwnableLibrary } from "../libraries/OwnableLibrary.sol";
import { LoopbackProxy } from "../LoopbackProxy.sol";
/**
* @dev Enables testnet contracts to be upgraded by the maintainer, since we don't want to waste time
*/
contract TestnetGovernanceProxy is LoopbackProxy {
modifier onlyOwner {
require(OwnableLibrary.getOwner() == msg.sender, "Not an owner");
_;
}
constructor(address _logic, bytes memory _data) public payable LoopbackProxy(_logic, _data) {
OwnableLibrary.setOwner(msg.sender);
}
function getCurrentOwner() external view returns (address) {
return OwnableLibrary.getOwner();
}
function changeOwner(address newOwner) external onlyOwner {
OwnableLibrary.setOwner(newOwner);
}
function upgradeToOwner(address newImplementation) external onlyOwner {
_upgradeTo(newImplementation);
}
function callToOwner(address target, bytes memory data) external payable onlyOwner {
(bool success, bytes memory returnData) = target.call{ value: msg.value }(data);
if (!success) {
assembly {
revert(add(32, returnData), mload(returnData))
}
}
}
function delegateToOwner(address target, bytes memory data) external payable onlyOwner {
(bool success, bytes memory returnData) = target.delegatecall(data);
if (!success) {
assembly {
revert(add(32, returnData), mload(returnData))
}
}
}
}

View File

@ -1,23 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { OwnableLibrary } from "../libraries/OwnableLibrary.sol";
import { LoopbackProxy } from "../LoopbackProxy.sol";
/**
* @dev Enables testnet contracts to be upgraded by the maintainer, since we don't want to waste time
*/
contract TestnetProxy is LoopbackProxy {
modifier onlyOwner {
require(OwnableLibrary.getOwner() == msg.sender, "Not an owner");
_;
}
constructor(address _logic, bytes memory _data) public payable LoopbackProxy(_logic, _data) {
OwnableLibrary.setOwner(msg.sender);
}
function upgradeToOwner(address newImplementation) external onlyOwner {
_upgradeTo(newImplementation);
}
}

View File

@ -9,8 +9,13 @@ import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol"
contract TornadoVault {
using SafeERC20 for IERC20;
address internal constant TornTokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
address internal constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address internal immutable TornTokenAddress;
address internal immutable GovernanceAddress;
constructor(address _torn, address _governance) public {
TornTokenAddress = _torn;
GovernanceAddress = _governance;
}
/// @notice withdraws TORN from the contract
/// @param amount amount to withdraw

View File

@ -1,6 +1,37 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "hardhat-storage-layout";
import fs from 'fs';
import path from 'path';
import process from 'process';
import { task, HardhatUserConfig } from 'hardhat/config';
import '@nomicfoundation/hardhat-toolbox';
import '@nomicfoundation/hardhat-ethers';
import 'hardhat-storage-layout';
task('flatten:all', 'Flatten all contracts each file under flatten directory')
.setAction(async (taskArgs, hre) => {
const allFilesAndFolders = fs.readdirSync('contracts', { recursive: true }) as Array<string>;
const allFolders = allFilesAndFolders.filter(f => fs.statSync(path.join('contracts', f)).isDirectory());
const allFiles = allFilesAndFolders.filter(f => !allFolders.includes(f));
fs.rmSync('flatten', { force: true, recursive: true });
fs.mkdirSync('flatten');
allFolders.forEach(f => {
fs.mkdirSync(path.join('flatten', f), { recursive: true });
});
await Promise.all(allFiles.map(async (f) => {
const contract = path.join('contracts', f);
const contractTo = path.join('flatten', f);
try {
const flatten = await hre.run('flatten:get-flattened-sources', { files: [contract] });
fs.writeFileSync(contractTo, flatten);
console.log(`Wrote ${contractTo} contract`);
} catch (e) {
// Catching circular contracts
console.log(`Failed to write ${contractTo} contract`);
console.log(e);
}
}));
});
const config: HardhatUserConfig = {
defaultNetwork: 'hardhat',
@ -28,6 +59,22 @@ const config: HardhatUserConfig = {
],
},
networks: {
develop: {
url: process.env.RPC_URL || '',
accounts: {
mnemonic: process.env.MNEMONIC || 'test test test test test test test test test test test junk',
initialIndex: Number(process.env.MNEMONIC_INDEX) || 0,
},
gasPrice: 50000,
},
sepolia: {
url: process.env.RPC_URL || 'https://rpc.sepolia.org',
accounts: {
mnemonic: process.env.MNEMONIC || 'test test test test test test test test test test test junk',
initialIndex: Number(process.env.MNEMONIC_INDEX) || 0,
},
gasPrice: 50000,
},
hardhat: {},
},
};

View File

@ -4,10 +4,13 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"lint": "eslint test --ext .ts --fix"
"compile": "hardhat compile && hardhat flatten:all",
"deploy": "hardhat run ./scripts/deploy.ts",
"lint": "eslint . --ext .ts --fix"
},
"files": [
"contracts",
"scripts",
"hardhat.config.ts",
"README.md",
"tsconfig.json",

355
scripts/deploy.ts Normal file
View File

@ -0,0 +1,355 @@
import fs from 'fs';
import path from 'path';
import hardhat, { ethers } from 'hardhat';
import {
type ERC20Mock,
type InstanceRegistry,
ERC20Mock__factory,
TORN__factory,
Governance__factory,
ETHTornado__factory,
ERC20Tornado__factory,
Vesting__factory,
TestnetGovernanceProxy__factory,
TestnetAdminProxy__factory,
GasCompensationVault__factory,
InstanceRegistry__factory,
GovernanceProposalStateUpgrade__factory,
RelayerRegistry__factory,
TornadoStakingRewards__factory,
TornadoRouter__factory,
TornadoVault__factory,
Echoer__factory,
TestnetFeeManager__factory,
} from '../typechain-types';
import type { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
const {
ZeroAddress,
parseEther,
deployContract
} = ethers;
type contracts = {
[key in string]: {
DAI: string,
ens: string,
}
}
const contracts: contracts = {
hardhat: {
DAI: '',
ens: '',
},
develop: {
DAI: '',
ens: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
},
sepolia: {
// https://staging.aave.com/faucet/
DAI: '0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357',
ens: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
}
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const toFixedHex = (number: number, length = 32) => {
return '0x' + BigInt(number).toString(16).padStart(length * 2, '0');
};
async function deployHasher(owner: SignerWithAddress) {
const bytecode = fs.readFileSync(path.join(__dirname, './hasherBytecode.txt'), { encoding: 'utf8' });
const txReceipt = await owner.sendTransaction({ data: bytecode }).then(t => t.wait());
return txReceipt?.contractAddress as unknown as string;
}
async function deployInstances() {
const [owner] = await ethers.getSigners();
const { DAI: daiAddress } = contracts[hardhat.network.name];
let DAI: ERC20Mock;
if (!daiAddress) {
DAI = await new ERC20Mock__factory(owner).deploy();
await DAI.waitForDeployment();
await DAI.mint(owner.address, parseEther('100000000')).then(t => t.wait());
} else {
DAI = ERC20Mock__factory.connect(daiAddress, owner);
}
const Hasher = await deployHasher(owner);
const Verifier = await deployContract('Verifier');
await Verifier.waitForDeployment();
const ETHTornadoFactory = new ETHTornado__factory(owner);
const ETHTornado1 = await ETHTornadoFactory.deploy(Verifier.target, Hasher, parseEther('0.1'), 20);
await ETHTornado1.waitForDeployment();
const ETHTornado2 = await ETHTornadoFactory.deploy(Verifier.target, Hasher, parseEther('1'), 20);
await ETHTornado2.waitForDeployment();
const ETHTornado3 = await ETHTornadoFactory.deploy(Verifier.target, Hasher, parseEther('10'), 20);
await ETHTornado3.waitForDeployment();
const ETHTornado4 = await ETHTornadoFactory.deploy(Verifier.target, Hasher, parseEther('100'), 20);
await ETHTornado4.waitForDeployment();
// Check if deposit works
// console.log(await ETHTornado1.deposit(toFixedHex(42), { value: parseEther('0.1') }).then(t => t.wait()))
const DAITornadoFactory = new ERC20Tornado__factory(owner);
const DAITornado1 = await DAITornadoFactory.deploy(Verifier.target, Hasher, parseEther('100'), 20, DAI.target);
await DAITornado1.waitForDeployment();
const DAITornado2 = await DAITornadoFactory.deploy(Verifier.target, Hasher, parseEther('1000'), 20, DAI.target);
await DAITornado2.waitForDeployment();
const DAITornado3 = await DAITornadoFactory.deploy(Verifier.target, Hasher, parseEther('10000'), 20, DAI.target);
await DAITornado3.waitForDeployment();
const DAITornado4 = await DAITornadoFactory.deploy(Verifier.target, Hasher, parseEther('100000'), 20, DAI.target);
await DAITornado4.waitForDeployment();
const ethInstance = {
isERC20: false,
token: ZeroAddress,
state: 1,
uniswapPoolSwappingFee: 0,
protocolFeePercentage: 0,
};
const daiInstance = {
isERC20: true,
token: DAI.target,
state: 1,
uniswapPoolSwappingFee: 3000,
protocolFeePercentage: 0,
};
const instances = [
{
addr: ETHTornado1.target,
instance: ethInstance,
},
{
addr: DAITornado2.target,
instance: {
...ethInstance,
protocolFeePercentage: 30,
},
},
{
addr: ETHTornado3.target,
instance: {
...ethInstance,
protocolFeePercentage: 30,
},
},
{
addr: ETHTornado4.target,
instance: {
...ethInstance,
protocolFeePercentage: 30,
},
},
{
addr: DAITornado1.target,
instance: daiInstance,
},
{
addr: DAITornado2.target,
instance: daiInstance,
},
{
addr: DAITornado3.target,
instance: {
...daiInstance,
protocolFeePercentage: 30,
},
},
{
addr: DAITornado4.target,
instance: {
...daiInstance,
protocolFeePercentage: 30,
},
},
];
console.log({
DAI: DAI.target,
Hasher: Hasher,
Verifier: Verifier.target,
instances,
});
return {
DAI,
Hasher,
Verifier,
instances,
};
}
async function deployGovernance(instances: InstanceRegistry.TornadoStruct[]): Promise<void> {
const [owner] = await ethers.getSigners();
const { ENS: ensAddress } = contracts[hardhat.network.name];
// Deploy V1 logic contact first
const GovernanceV1 = await new Governance__factory(owner).deploy();
await GovernanceV1.waitForDeployment();
// This is what almost every contract would need for constructor args
const GovernanceProxy = await new TestnetGovernanceProxy__factory(owner).deploy(GovernanceV1.target, '0x');
await GovernanceProxy.waitForDeployment();
const TORN = await new TORN__factory(owner).deploy(
GovernanceProxy.target,
0,
[
{
to: owner.address,
amount: parseEther('10000000')
}
]
);
await TORN.waitForDeployment();
// Initialize governance v1 with TORN address
await (Governance__factory.connect(GovernanceProxy.target as string, owner)).initialize(TORN.target).then(t => t.wait());
// Create governance vesting contract
const GovernanceVesting = await new Vesting__factory(owner).deploy(
TORN.target,
GovernanceProxy.target,
0,
3,
60
);
await GovernanceVesting.waitForDeployment();
await TORN.transfer(GovernanceVesting.target, parseEther('2500000')).then(t => t.wait());
// Deploy other governance contracts
const ProxyFactory = new TestnetAdminProxy__factory(owner);
// Deploy gas compensation vault
const GasCompensationVault = await new GasCompensationVault__factory(owner).deploy(GovernanceProxy.target);
await GasCompensationVault.waitForDeployment();
// Deploy user vault
const TornadoVault = await new TornadoVault__factory(owner).deploy(TORN.target, GovernanceProxy.target);
await TornadoVault.waitForDeployment();
// Deploy instance registry
const InstanceRegistryImpl = await new InstanceRegistry__factory(owner).deploy(GovernanceProxy.target);
await InstanceRegistryImpl.waitForDeployment();
const InstanceRegistryProxy = await ProxyFactory.deploy(InstanceRegistryImpl.target, GovernanceProxy.target, '0x');
await InstanceRegistryProxy.waitForDeployment();
// Deploy FeeManager
const FeeManagerImpl = await new TestnetFeeManager__factory(owner).deploy(TORN.target, GovernanceProxy.target, InstanceRegistryProxy.target);
await FeeManagerImpl.waitForDeployment();
const FeeManagerProxy = await ProxyFactory.deploy(FeeManagerImpl.target, GovernanceProxy.target, '0x');
await FeeManagerProxy.waitForDeployment();
// Deploy RelayerRegistry & TornadoStakingRewards
const RelayerRegistryMock = await new RelayerRegistry__factory(owner).deploy(
TORN.target,
GovernanceProxy.target,
ensAddress || ZeroAddress,
ZeroAddress, // Use this as zero address as we don't know the proxy address yet
FeeManagerProxy.target,
);
await RelayerRegistryMock.waitForDeployment();
const RelayerRegistryProxy = await ProxyFactory.deploy(RelayerRegistryMock.target, GovernanceProxy.target, '0x');
await RelayerRegistryProxy.waitForDeployment();
const TornadoStakingRewardsImpl = await new TornadoStakingRewards__factory(owner).deploy(
GovernanceProxy.target,
TORN.target,
RelayerRegistryProxy.target
);
await TornadoStakingRewardsImpl.waitForDeployment();
const TornadoStakingRewardsProxy = await ProxyFactory.deploy(TornadoStakingRewardsImpl.target, GovernanceProxy.target, '0x');
await TornadoStakingRewardsProxy.waitForDeployment();
const RelayerRegistryImpl = await new RelayerRegistry__factory(owner).deploy(
TORN.target,
GovernanceProxy.target,
ensAddress || ZeroAddress,
TornadoStakingRewardsProxy.target,
FeeManagerProxy.target,
);
await RelayerRegistryImpl.waitForDeployment();
await (TestnetAdminProxy__factory.connect(RelayerRegistryProxy.target as string, owner)).upgradeToOwner(RelayerRegistryImpl.target).then(t => t.wait());
// Deploy Echoer
const Echoer = await new Echoer__factory(owner).deploy();
await Echoer.waitForDeployment();
// Deploy TornadoRouter
const TornadoRouter = await new TornadoRouter__factory(owner).deploy(
GovernanceProxy.target,
InstanceRegistryProxy.target,
RelayerRegistryProxy.target
);
await TornadoRouter.waitForDeployment();
// Initialize InstanceRegistry
await (InstanceRegistry__factory.connect(InstanceRegistryProxy.target as string, owner)).initialize(
instances,
TornadoRouter.target
).then(t => t.wait());
// Initialize RelayerRegistry
await (RelayerRegistry__factory.connect(RelayerRegistryProxy.target as string, owner)).initialize(
TornadoRouter.target
).then(t => t.wait());
// Upgrade Governance
const GovernanceV5 = await new GovernanceProposalStateUpgrade__factory(owner).deploy(
TornadoStakingRewardsProxy.target,
GasCompensationVault.target,
TornadoVault.target
);
await GovernanceV5.waitForDeployment();
await (TestnetGovernanceProxy__factory.connect(GovernanceProxy.target as string, owner)).upgradeToOwner(GovernanceV5.target).then(t => t.wait());
// Finalize contracts
await owner.sendTransaction({ to: GasCompensationVault.target, value: parseEther('0.1') }).then(t => t.wait());
console.log({
TORN: TORN.target,
GovernanceProxy: GovernanceProxy.target,
GovernanceV1: GovernanceV1.target,
GovernanceV5: GovernanceV5.target,
GovernanceVesting: GovernanceVesting.target,
GasCompensationVault: GasCompensationVault.target,
TornadoVault: TornadoVault.target,
InstanceRegistryProxy: InstanceRegistryProxy.target,
InstanceRegistryImpl: InstanceRegistryImpl.target,
FeeManagerProxy: FeeManagerProxy.target,
FeeManagerImpl: FeeManagerImpl.target,
RelayerRegistryProxy: RelayerRegistryProxy.target,
RelayerRegistryImpl: RelayerRegistryImpl.target,
RelayerRegistryMock: RelayerRegistryMock.target,
TornadoStakingRewardsProxy: TornadoStakingRewardsProxy.target,
TornadoStakingRewardsImpl: TornadoStakingRewardsImpl.target,
TornadoRouter: TornadoRouter.target,
Echoer: Echoer.target,
});
}
async function deploy() {
const { instances } = await deployInstances();
await deployGovernance(instances);
}
deploy();

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@
"resolveJsonModule": true,
"types": ["@types/node"]
},
"include": ["test/**/*"],
"exclude": ["node_modules"]
"include": ["./scripts", "./test", "./typechain-types"],
"exclude": ["node_modules"],
"files": ["./hardhat.config.ts"]
}