Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Alexander Drygin | 6464973b10 | |
Drygin | 5091c0998d | |
Drygin | 0b92bdf73f | |
Drygin | 68b42a2c52 | |
Drygin | 1904c9f9c5 | |
Drygin | 9083416f44 | |
Drygin | debbd60010 | |
Drygin | 74f38d68b0 | |
Drygin | 74b6021aa7 | |
Drygin | 1b50b703c2 | |
Drygin | 1bee8e29f6 |
|
@ -21,6 +21,11 @@ jobs:
|
|||
- run: yarn install
|
||||
- run: yarn hardhat compile
|
||||
- run: yarn lint
|
||||
- run: yarn coverage
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: yarn hardhat test
|
||||
- name: Telegram Failure Notification
|
||||
uses: appleboy/telegram-action@0.0.7
|
||||
|
|
|
@ -1,6 +1,111 @@
|
|||
node_modules
|
||||
cache
|
||||
artifacts
|
||||
build
|
||||
dist
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
artifacts
|
||||
cache
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
.vscode
|
|
@ -5,4 +5,6 @@ artifacts
|
|||
build
|
||||
dist
|
||||
README.md
|
||||
contracts/v2-vault-and-gas/libraries/EtherSend.sol
|
||||
contracts/v2-vault-and-gas/libraries/EtherSend.sol
|
||||
coverage
|
||||
coverage.json
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
skipFiles: [],
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
}
|
||||
],
|
||||
"quotes": ["error", "double"],
|
||||
"indent": ["error", 2]
|
||||
"indent": ["error", 2],
|
||||
"compiler-version": ["error", "^0.6.0"]
|
||||
},
|
||||
"plugins": ["prettier"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"solidity.compileUsingRemoteVersion": "v0.6.12+commit.27d51765"
|
||||
}
|
47
README.md
47
README.md
|
@ -1,14 +1,49 @@
|
|||
# tornado-governance
|
||||
# Tornado governance [![build status](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml/badge.svg)](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml) [![Coverage Status](https://coveralls.io/repos/github/tornadocash/tornado-governance/badge.svg?branch=master)](https://coveralls.io/github/tornadocash/tornado-governance?branch=master)
|
||||
|
||||
## Description
|
||||
This repository holds all the tornado.cash governance upgrade and original governance contracts.
|
||||
The highest governance version right now is version 2, which includes a gas and vault upgrade.
|
||||
This repository holds all the tornado.cash governance upgrades and original governance contracts.
|
||||
|
||||
## Setup
|
||||
## Documentation
|
||||
All high-level documentation can be find [here](https://docs.tornado.cash/general/governance).
|
||||
|
||||
## Code architecture
|
||||
Tornado governance infrastructure consists of two types of repository:
|
||||
1. **Governance repository** (this one) - contains the original governance contracts and parts of proposals that upgrade governance itself via loopback proxy. So here you can compile the actual version of the governance contract.
|
||||
2. **Proposal repository** - a separate repository for each governance proposal. It contains the full codebase of a proposal.
|
||||
|
||||
### Loopback proxy
|
||||
[Loopback proxy](https://github.com/tornadocash/tornado-governance/blob/master/contracts/v1/LoopbackProxy.sol) is a special type of proxy contract that is used to add the ability to upgrade the proxy itself. This way governance proposals can upgrade governance implementation.
|
||||
|
||||
### Proposal creation manual
|
||||
To create your custom governance proposal you need to:
|
||||
1. Create a proposal repository (for [example](https://github.com/Rezan-vm/tornado-relayer-registry)):
|
||||
- a proposal is executed from the governance contract using delegatecall of __executeProposal()__ method
|
||||
- as a proposal is executed using delegatecall, it should not store any storage variables - use constants and immutable variables instead
|
||||
2. If your proposal is upgrading governance itself, you need to create a pull request to the governance repository. PR should add folder with governance contract upgrade (separate folder in contracts folder - for [example](https://github.com/tornadocash/tornado-governance/pull/6/commits/5f36d5744a9f279a58e9ba1f0e0cd9d493af41c7)).
|
||||
3. Deploy proposal. The proposal must be smart contracts with verified code.
|
||||
4. Go to Tornado governance [UI](https://tornadocash.eth.limo/governance) to start the proposal voting process.
|
||||
|
||||
|
||||
## Tests/Coverage
|
||||
|
||||
Setting up the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tornadocash/tornado-governance.git
|
||||
yarn
|
||||
cp .env.example .env # you must enter your details into .env
|
||||
yarn test
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Please fill out .env according to the template provided in it. Please ensure that all of the example values are set to the correct addresses.
|
||||
|
||||
To run test scripts:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
To run tests coverage:
|
||||
|
||||
```bash
|
||||
yarn coverage
|
||||
```
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
governance: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||
forkBlockNumber: 14352372,
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import { GovernanceVaultUpgrade } from "../vault/GovernanceVaultUpgrade.sol";
|
||||
import { GovernanceVaultUpgrade } from "./GovernanceVaultUpgrade.sol";
|
||||
import { GasCompensator } from "./GasCompensator.sol";
|
||||
import { Math } from "@openzeppelin/contracts/math/Math.sol";
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import { Governance } from "../../v1/Governance.sol";
|
||||
import { Governance } from "../v1/Governance.sol";
|
||||
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
|
||||
import { ITornadoVault } from "../interfaces/ITornadoVault.sol";
|
||||
import { ITornadoVault } from "./interfaces/ITornadoVault.sol";
|
||||
|
||||
/// @title Version 2 Governance contract of the tornado.cash governance
|
||||
contract GovernanceVaultUpgrade is Governance {
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IGovernanceMultisigAddress {
|
||||
function returnMultisigAddress() external pure returns (address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Contract which hold governance information. Useful for avoiding code duplication.
|
||||
* */
|
||||
contract ImmutableGovernanceInformation {
|
||||
address internal constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
address internal constant TornTokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == GovernanceAddress, "only governance");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev this modifier calls the pure governance returnMultisigAddress() function,
|
||||
* if governance version is not -> vault-and-gas upgrade <= version
|
||||
* then this will not work!
|
||||
*/
|
||||
modifier onlyMultisig() {
|
||||
require(msg.sender == IGovernanceMultisigAddress(GovernanceAddress).returnMultisigAddress(), "only multisig");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Function to return a payable version of the governance address.
|
||||
* @return payable version of the address
|
||||
* */
|
||||
function returnPayableGovernance() internal pure returns (address payable) {
|
||||
return payable(GovernanceAddress);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,6 @@
|
|||
# Tornado Governance Changes Documentation
|
||||
# Tornado Governance Changes
|
||||
|
||||
`LotteryAndVaultProposal.sol`, if executed, modifies the tornado.cash governance contract in multiple ways.
|
||||
|
||||
This serves as documentation for all functions which are being added or modified and which have relevant functionality:
|
||||
|
||||
## Governance (Governance.sol)
|
||||
|
||||
The `Governance` contract available in the `tornado-governance` repository modifies the original Governance source code to enable overriding functions via inheritance. This has been submitted via PR.
|
||||
|
||||
This is a non issue, as logic and memory slots are left unaffected and properly referenced to. `tornado-governance` is being imported as a package.
|
||||
Governance proposal [repo](https://github.com/peppersec/tornado-vault-and-gas-proposal).
|
||||
|
||||
## Governance Vault Upgrade (GovernanceVaultUpgrade.sol)
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { LoopbackProxy } from "tornado-governance/contracts/LoopbackProxy.sol";
|
||||
|
||||
import { TornadoVault } from "./vault/TornadoVault.sol";
|
||||
import { TornadoAuctionHandler } from "./auction/TornadoAuctionHandler.sol";
|
||||
import { GovernanceGasUpgrade } from "./gas/GovernanceGasUpgrade.sol";
|
||||
|
||||
import { IGovernanceVesting } from "./interfaces/IGovernanceVesting.sol";
|
||||
import { ImmutableGovernanceInformation } from "./ImmutableGovernanceInformation.sol";
|
||||
|
||||
/**
|
||||
* @notice This proposal should upgrade governance to the vault and gas version without breaking any logic.
|
||||
* */
|
||||
contract VaultAndGasProposal is ImmutableGovernanceInformation {
|
||||
using SafeMath for uint256;
|
||||
|
||||
IGovernanceVesting public constant GovernanceVesting = IGovernanceVesting(0x179f48C78f57A3A78f0608cC9197B8972921d1D2);
|
||||
address public immutable gasCompLogic;
|
||||
/// @notice the new voting period we would like to include
|
||||
uint256 public immutable votingPeriod;
|
||||
|
||||
event TornadoAuctionHandlerCreated(address indexed handler);
|
||||
|
||||
constructor(address _gasCompLogic, uint256 _votingPeriod) public {
|
||||
gasCompLogic = _gasCompLogic;
|
||||
votingPeriod = _votingPeriod;
|
||||
}
|
||||
|
||||
/// @notice the entry point for the governance upgrade logic execution
|
||||
/// @dev this function bundles all of the initialization logic for all of the contracts of the project
|
||||
function executeProposal() external {
|
||||
address vault = address(new TornadoVault());
|
||||
|
||||
LoopbackProxy(returnPayableGovernance()).upgradeTo(address(new GovernanceGasUpgrade(gasCompLogic, vault)));
|
||||
|
||||
GovernanceGasUpgrade newGovernance = GovernanceGasUpgrade(returnPayableGovernance());
|
||||
IERC20 tornToken = IERC20(TornTokenAddress);
|
||||
|
||||
newGovernance.setVotingPeriod(votingPeriod);
|
||||
|
||||
/**
|
||||
The below variable holds the total amount of TORN outflows from all of the proposal executions,
|
||||
which will be used to calculate the proper amount of TORN for transfer to Governance.
|
||||
For an explanation as to how this variable has been calculated with these fix values, please look at:
|
||||
https://github.com/h-ivor/tornado-lottery-period/blob/production/scripts/balance_estimation.md
|
||||
*/
|
||||
uint256 totalOutflowsOfProposalExecutions = 120000000000000000000000 +
|
||||
22916666666666666666666 +
|
||||
54999999999999969408000 -
|
||||
27e18;
|
||||
|
||||
require(
|
||||
tornToken.transfer(
|
||||
address(newGovernance.userVault()),
|
||||
(tornToken.balanceOf(address(this))).sub(GovernanceVesting.released().sub(totalOutflowsOfProposalExecutions))
|
||||
),
|
||||
"TORN: transfer failed"
|
||||
);
|
||||
|
||||
uint96 amountOfTornToAuctionOff = 787 ether;
|
||||
uint96 minBuyAmount = 11 ether;
|
||||
uint256 minBidInTorn = 10 ether;
|
||||
uint256 fundingThreshold = 9 ether;
|
||||
|
||||
TornadoAuctionHandler auctionHandler = new TornadoAuctionHandler();
|
||||
|
||||
emit TornadoAuctionHandlerCreated(address(auctionHandler));
|
||||
|
||||
tornToken.transfer(address(auctionHandler), amountOfTornToAuctionOff);
|
||||
|
||||
/**
|
||||
As with above, please see:
|
||||
https://github.com/h-ivor/tornado-lottery-period/blob/production/contracts/auction/TornadoAuctionHandler.sol
|
||||
*/
|
||||
auctionHandler.initializeAuction(
|
||||
block.timestamp + 5 days,
|
||||
amountOfTornToAuctionOff,
|
||||
minBuyAmount,
|
||||
minBidInTorn,
|
||||
fundingThreshold
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
# Auctioning some Tornado for compensations ETH
|
||||
|
||||
To boost voting activity, one of our ideas is to compensate gas used for voting on proposals.
|
||||
Both for the castVote and castDelegatedVote functionality.
|
||||
|
||||
To make this as smooth as possible, we will compensate users directly in **ETH** (non-wrapped) for voting.
|
||||
The priority fee is not compensated for, as to make exploiting the compensations unnecessary and unprofitable.
|
||||
|
||||
In order to receive ETH, TORN will be auctioned off by the governance contract with the help of a auction helper
|
||||
(see contracts/auction/TornadoAuctionHandler.sol).
|
||||
|
||||
This contract has two functionalities:
|
||||
|
||||
- Initiate an auction.
|
||||
|
||||
- Convert all WETH it holds into ETH and send to Governance (callable by anyone).
|
||||
|
||||
This way, Governance does not need to handle WETH swap logic (would require extra logic) and ETH will be directly sent to the governance contract.
|
||||
|
||||
The initializeAuction function takes a couple of parameters:
|
||||
|
||||
```
|
||||
function initializeAuction(
|
||||
uint256 _auctionEndDate,
|
||||
uint96 _auctionedSellAmount,
|
||||
uint96 _minBuyAmount,
|
||||
uint256 _minBidPerOrder,
|
||||
uint256 _minFundingThreshold
|
||||
) external onlyGovernance {
|
||||
```
|
||||
|
||||
- \_auctionEndDate -> the auction end date expressed in UNIX format.
|
||||
- \_auctionedSellAmount -> the amount of TORN to be sold in the auction.
|
||||
- \_minBuyAmount -> this variable helps to define the minimum price via the following formula: \_auctionedSellAmount/\_minBuyAmount, in other words the minimum amount of TORN per ETH.
|
||||
- \_minBidPerOrder -> minimum buy amount per a single order (of tokens being auctioned), is also used to prevent users from buying too low amounts and hurting themselves.
|
||||
- \_minFundingThreshold -> minimum amount of buy tokens (ETH) for the ENTIRE auction. If this is not reached, the auction reverts and all tokens are sent back to their original owners.
|
||||
|
||||
This function does not take all the parameters for initializing the auction, the entire function may be seen below, some were left out of convenience:
|
||||
|
||||
```
|
||||
IEasyAuction(EasyAuctionAddress).initiateAuction(
|
||||
IERC20(TornTokenAddress),
|
||||
IERC20(WETHAddress),
|
||||
0, // orderCancellationEndDate
|
||||
_auctionEndDate,
|
||||
_auctionedSellAmount,
|
||||
_minBuyAmount,
|
||||
_minBidPerOrder,
|
||||
_minFundingThreshold,
|
||||
true, // isAtomicClosureAllowed
|
||||
address(0x0000000000000000000000000000000000000000), // access
|
||||
new bytes(0) // access
|
||||
);
|
||||
```
|
||||
|
||||
- Addresses of the tokens being bought/sold (ETH/TORN).
|
||||
- orderCancellationEndDate -> date until order can be cancelled. For us, this is 0, meaning orders can't be cancelled once set.
|
||||
- isAtomicClosureAllowed -> when auction end date is reached, a participant may set a last order in exchange for closing the auction, meaning it incentivizes the user to end the auction (gas payments, time saving) by giving him a risk-free action at the end. For us, false, due to tests showing that dust collection might not work if this is used.
|
||||
- Last two fields are for access management, we have no whitelist for the auction, thus redundant and set to 0 for us.
|
|
@ -1,58 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
import { IWETH } from "./interfaces/IWETH.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { EtherSend } from "../libraries/EtherSend.sol";
|
||||
import { IEasyAuction } from "./interfaces/IEasyAuction.sol";
|
||||
import { ImmutableGovernanceInformation } from "../ImmutableGovernanceInformation.sol";
|
||||
|
||||
/// @notice Handler which should help governance start an auction and transfer results of an auction to governance.
|
||||
/// @dev The reasoning behind this contract is to not bloat governance with unnecessary logic.
|
||||
contract TornadoAuctionHandler is ImmutableGovernanceInformation {
|
||||
using EtherSend for address;
|
||||
|
||||
address public constant EasyAuctionAddress = 0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101;
|
||||
address public constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
|
||||
/// @notice main auction initialization function, please see: https://github.com/h-ivor/tornado-lottery-period/blob/only-vault-and-gas/contracts/auction/Auction.md
|
||||
/// @dev calls easy auction deployed on eth mainnet
|
||||
function initializeAuction(
|
||||
uint256 _auctionEndDate,
|
||||
uint96 _auctionedSellAmount,
|
||||
uint96 _minBuyAmount,
|
||||
uint256 _minBidPerOrder,
|
||||
uint256 _minFundingThreshold
|
||||
) external onlyGovernance {
|
||||
require(IERC20(TornTokenAddress).balanceOf(address(this)) >= _auctionedSellAmount, "torn balance not enough");
|
||||
IERC20(TornTokenAddress).approve(EasyAuctionAddress, _auctionedSellAmount);
|
||||
|
||||
IEasyAuction(EasyAuctionAddress).initiateAuction(
|
||||
IERC20(TornTokenAddress),
|
||||
IERC20(WETHAddress),
|
||||
0,
|
||||
_auctionEndDate,
|
||||
_auctionedSellAmount,
|
||||
_minBuyAmount,
|
||||
_minBidPerOrder,
|
||||
_minFundingThreshold,
|
||||
false,
|
||||
address(0x0000000000000000000000000000000000000000),
|
||||
new bytes(0)
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice function to transfer all eth and TORN dust to governance
|
||||
function convertAndTransferToGovernance() external {
|
||||
IWETH(WETHAddress).withdraw(IWETH(WETHAddress).balanceOf(address(this)));
|
||||
if (address(this).balance > 0) require(GovernanceAddress.sendEther(address(this).balance), "pay fail");
|
||||
if (IERC20(TornTokenAddress).balanceOf(address(this)) > 0)
|
||||
IERC20(TornTokenAddress).transfer(GovernanceAddress, IERC20(TornTokenAddress).balanceOf(address(this)));
|
||||
}
|
||||
|
||||
/// @notice receive eth that should only allow mainnet WETH to send eth
|
||||
receive() external payable {
|
||||
require(msg.sender == WETHAddress, "only weth");
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
interface IEasyAuction {
|
||||
function initiateAuction(
|
||||
IERC20 _auctioningToken,
|
||||
IERC20 _biddingToken,
|
||||
uint256 orderCancellationEndDate,
|
||||
uint256 auctionEndDate,
|
||||
uint96 _auctionedSellAmount,
|
||||
uint96 _minBuyAmount,
|
||||
uint256 minimumBiddingAmountPerOrder,
|
||||
uint256 minFundingThreshold,
|
||||
bool isAtomicClosureAllowed,
|
||||
address accessManagerContract,
|
||||
bytes memory accessManagerContractData
|
||||
) external returns (uint256);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface IPayableGovernance {
|
||||
function receiveEther() external payable returns (bool);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface IWETH {
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
|
||||
function deposit() external payable;
|
||||
|
||||
function withdraw(uint256 wad) external;
|
||||
|
||||
function totalSupply() external view returns (uint256);
|
||||
|
||||
function approve(address guy, uint256 wad) external returns (bool);
|
||||
|
||||
function transfer(address dst, uint256 wad) external returns (bool);
|
||||
|
||||
function transferFrom(
|
||||
address src,
|
||||
address dst,
|
||||
uint256 wad
|
||||
) external returns (bool);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import { EtherSend } from "../libraries/EtherSend.sol";
|
||||
|
||||
interface IPayableGovernance {
|
||||
function receiveEther() external payable returns (bool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice this contract should store ether for gas compensations and also retrieve the basefee
|
||||
* */
|
||||
contract GasCompensationVault {
|
||||
using EtherSend for address;
|
||||
|
||||
address private constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == GovernanceAddress, "only gov");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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");
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice receive ether function, does nothing but receive ether
|
||||
* */
|
||||
receive() external payable {}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IGovernanceVesting {
|
||||
function released() external view returns (uint256);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12 || ^0.8.7;
|
||||
|
||||
/// @notice very short library which implements a method to transfer ether via <address>.call
|
||||
library EtherSend {
|
||||
/**
|
||||
* @notice function to transfer ether via filling the value field of a call
|
||||
* @dev DICLAIMER: you must handle the possibility of reentrancy when using this function!!!
|
||||
* @param to address to be transferred to
|
||||
* @param amount amount to be transferred
|
||||
* @return success true if transfer successful
|
||||
* */
|
||||
function sendEther(address to, uint256 amount) internal returns (bool success) {
|
||||
(success, ) = payable(to).call{ value: amount }("");
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
contract GasCompensationVault {
|
||||
address private constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == GovernanceAddress, "only gov");
|
||||
_;
|
||||
}
|
||||
|
||||
function compensateGas(address recipient, uint256 amount) external onlyGovernance {
|
||||
if (address(this).balance == 0) return;
|
||||
require(
|
||||
(amount > address(this).balance) ? payable(recipient).send(address(this).balance) : payable(recipient).send(amount),
|
||||
"compensation failed"
|
||||
);
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
function getBasefee() external view returns (uint256) {
|
||||
return 5;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
import "tornado-governance/contracts/Governance.sol";
|
||||
import "../../v1/Governance.sol";
|
||||
|
||||
contract MockProposal1 {
|
||||
contract MockProposal {
|
||||
address public constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
function executeProposal() external {
|
|
@ -1,16 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import { IterableOrderedOrderSet } from "@gnosis.pm/ido-contracts/contracts/libraries/IterableOrderedOrderSet.sol";
|
||||
|
||||
contract OrderEncoderHelper {
|
||||
function encodeOrder(
|
||||
uint64 userId,
|
||||
uint96 buyAmount,
|
||||
uint96 sellAmount
|
||||
) external pure returns (bytes32) {
|
||||
return IterableOrderedOrderSet.encodeOrder(userId, buyAmount, sellAmount);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
||||
|
||||
/// @title Vault which holds user funds
|
||||
contract TornadoVault {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address internal constant TornTokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
|
||||
address internal constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
/// @notice withdraws TORN from the contract
|
||||
/// @param amount amount to withdraw
|
||||
function withdrawTorn(address recipient, uint256 amount) external {
|
||||
require(msg.sender == GovernanceAddress, "only gov");
|
||||
IERC20(TornTokenAddress).safeTransfer(recipient, amount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import { GovernanceGasUpgrade } from "../v2-vault-and-gas/GovernanceGasUpgrade.sol";
|
||||
import { ITornadoStakingRewards } from "./interfaces/ITornadoStakingRewards.sol";
|
||||
|
||||
/**
|
||||
* @notice The Governance staking upgrade. Adds modifier to any un/lock operation to update rewards
|
||||
* @dev CONTRACT RISKS:
|
||||
* - if updateRewards reverts (should not happen due to try/catch) locks/unlocks could be blocked
|
||||
* - generally inherits risks from former governance upgrades
|
||||
*/
|
||||
contract GovernanceStakingUpgrade is GovernanceGasUpgrade {
|
||||
ITornadoStakingRewards public immutable Staking;
|
||||
|
||||
event RewardUpdateSuccessful(address indexed account);
|
||||
event RewardUpdateFailed(address indexed account, bytes indexed errorData);
|
||||
|
||||
constructor(
|
||||
address stakingRewardsAddress,
|
||||
address gasCompLogic,
|
||||
address userVaultAddress
|
||||
) public GovernanceGasUpgrade(gasCompLogic, userVaultAddress) {
|
||||
Staking = ITornadoStakingRewards(stakingRewardsAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This modifier should make a call to Staking to update the rewards for account without impacting logic on revert
|
||||
* @dev try / catch block to handle reverts
|
||||
* @param account Account to update rewards for.
|
||||
* */
|
||||
modifier updateRewards(address account) {
|
||||
try Staking.updateRewardsOnLockedBalanceChange(account, lockedBalance[account]) {
|
||||
emit RewardUpdateSuccessful(account);
|
||||
} catch (bytes memory errorData) {
|
||||
emit RewardUpdateFailed(account, errorData);
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
function lock(
|
||||
address owner,
|
||||
uint256 amount,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) public virtual override updateRewards(owner) {
|
||||
super.lock(owner, amount, deadline, v, r, s);
|
||||
}
|
||||
|
||||
function lockWithApproval(uint256 amount) public virtual override updateRewards(msg.sender) {
|
||||
super.lockWithApproval(amount);
|
||||
}
|
||||
|
||||
function unlock(uint256 amount) public virtual override updateRewards(msg.sender) {
|
||||
super.unlock(amount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
# Tornado Relayer Registry
|
||||
|
||||
Governance proposal [repo](https://github.com/Rezan-vm/tornado-relayer-registry).
|
||||
|
||||
Governance upgrade which includes a registry for relayer registration and staking mechanisms for the TORN token.
|
||||
|
||||
## Overview
|
||||
|
||||
1. Anyone can become a relayer by staking TORN into Registry contract.
|
||||
2. Minimum stake is governed by the Governance.
|
||||
3. Each Pool has its own fee % which is also set by the Governance.
|
||||
4. On every withdrawal via relayer, the relayer has to pay the Tornado Pool fee in TORN. The fee is deducted from his staked balance.
|
||||
5. All collected fees are stored into StakingReward contract.
|
||||
6. Any TORN holder can stake their TORN into Governance contract like they were before, but earning fees proportionately to their stake.
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface ITornadoStakingRewards {
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external;
|
||||
}
|
|
@ -6,8 +6,10 @@ require('hardhat-spdx-license-identifier')
|
|||
require('hardhat-storage-layout')
|
||||
require('hardhat-log-remover')
|
||||
require('hardhat-contract-sizer')
|
||||
require('solidity-coverage')
|
||||
|
||||
const config = require('./config')
|
||||
|
||||
require('./tasks/deploy_proposal.js')
|
||||
/**
|
||||
* @type import('hardhat/config').HardhatUserConfig
|
||||
*/
|
||||
|
@ -23,31 +25,13 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '0.8.7',
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '0.7.6',
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
forking: {
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
blockNumber: 13042331,
|
||||
blockNumber: config.forkBlockNumber,
|
||||
},
|
||||
initialBaseFeePerGas: 5,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tornado-governance",
|
||||
"version": "2.0.0",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
@ -16,7 +16,8 @@
|
|||
"test:all:f": "yarn prettier:fix && yarn test && yarn lint",
|
||||
"test:f": "yarn prettier:fix && yarn test",
|
||||
"clean": "yarn prettier:fix && yarn lint",
|
||||
"compile": "yarn prettier:fix && yarn hardhat compile"
|
||||
"compile": "yarn prettier:fix && yarn hardhat compile",
|
||||
"coverage": "yarn hardhat coverage"
|
||||
},
|
||||
"author": "Tornado.cash team <hello@tornado.cash>",
|
||||
"license": "MIT",
|
||||
|
@ -49,6 +50,7 @@
|
|||
"hardhat-storage-layout": "^0.1.6",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.17",
|
||||
"solhint-plugin-prettier": "^0.0.5"
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.21"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
require('dotenv').config()
|
||||
const { task } = require('hardhat/config')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
|
||||
task('deploy_proposal', 'deploy the lottery/vault upgrade proposal')
|
||||
.addParam('votingPeriod', 'the desired new voting period')
|
||||
.setAction(async (taskArgs, hre) => {
|
||||
const GasVaultFactory = await hre.ethers.getContractFactory(
|
||||
'contracts/basefee/GasCompensationVault.sol:GasCompensationVault',
|
||||
)
|
||||
const GasVaultContract = await GasVaultFactory.deploy()
|
||||
|
||||
await GasVaultContract.deployTransaction.wait(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: GasVaultContract.address,
|
||||
})
|
||||
|
||||
const ProposalFactory = await hre.ethers.getContractFactory('VaultAndGasProposal')
|
||||
const ProposalContract = await ProposalFactory.deploy(
|
||||
GasVaultContract.address,
|
||||
BigNumber.from(taskArgs.votingPeriod),
|
||||
)
|
||||
|
||||
await ProposalContract.deployTransaction.wait(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: ProposalContract.address,
|
||||
constructorArguments: [GasVaultContract.address, BigNumber.from(taskArgs.votingPeriod)],
|
||||
})
|
||||
|
||||
console.log('Successfully deployed proposal contract at: ', ProposalContract.address)
|
||||
})
|
|
@ -1,20 +0,0 @@
|
|||
require('dotenv').config()
|
||||
const { task } = require('hardhat/config')
|
||||
|
||||
task('propose_proposal', 'propose proposal that uses factory')
|
||||
.addParam('proposalAddress', 'address of proposal')
|
||||
.setAction(async (taskArgs, hre) => {
|
||||
const proposalName = 'lottery-and-vault-proposal'
|
||||
const signerArray = hre.ethers.getSigners()
|
||||
|
||||
const GovernanceContract = await hre.ethers.getContractAt(
|
||||
'Governance',
|
||||
'0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
)
|
||||
await GovernanceContract.propose(taskArgs.proposalAddress, proposalName)
|
||||
|
||||
const id = await GovernanceContract.latestProposalIds(signerArray[0].address)
|
||||
const state = await GovernanceContract.state(id)
|
||||
|
||||
console.log('Proposal with name: ', proposalName, ' proposed with id: ', id, ', has state: ', state)
|
||||
})
|
|
@ -1,93 +0,0 @@
|
|||
{
|
||||
"mainnet": {
|
||||
"tornado_cash_addresses": {
|
||||
"governance": "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce",
|
||||
"multisig": "0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4",
|
||||
"trees": "0x527653eA119F3E6a1F5BD18fbF4714081D7B31ce",
|
||||
"tornado_proxy": "0x722122dF12D4e14e13Ac3b6895a86e84145b6967"
|
||||
},
|
||||
"token_addresses": {
|
||||
"torn": "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C",
|
||||
"weth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"eth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"cdai": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
|
||||
"usdt": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
"wbtc": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
},
|
||||
"project_specific": {
|
||||
"contract_construction": {
|
||||
"RelayerRegistryData": {
|
||||
"tornado_pools": [
|
||||
"0x12d66f87a04a9e220743712ce6d9bb1b5616b8fc",
|
||||
"0x47ce0c6ed5b0ce3d3a51fdb1c52dc66a7c3c2936",
|
||||
"0x910cbd523d972eb0a6f4cae4618ad62622b39dbf",
|
||||
"0xa160cdab225685da1d56aa342ad8841c3b53f291",
|
||||
"0xd4b88df4d29f5cedd6857912842cff3b20c8cfa3",
|
||||
"0xfd8610d20aa15b7b2e3be39b396a1bc3516c7144",
|
||||
"0x07687e702b410fa43f4cb4af7fa097918ffd2730",
|
||||
"0x23773e65ed146a459791799d01336db287f25334",
|
||||
"0x22aaA7720ddd5388A3c0A3333430953C68f1849b",
|
||||
"0x03893a7c7463AE47D46bc7f091665f1893656003",
|
||||
"0x2717c5e28cf931547B621a5dddb772Ab6A35B701",
|
||||
"0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af",
|
||||
"0xd96f2B1c14Db8458374d9Aca76E26c3D18364307",
|
||||
"0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D",
|
||||
"0x169AD27A470D064DEDE56a2D3ff727986b15D52B",
|
||||
"0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f",
|
||||
"0x178169B423a011fff22B9e3F3abeA13414dDD0F1",
|
||||
"0x610B717796ad172B316836AC95a2ffad065CeaB4",
|
||||
"0xbB93e510BbCD0B7beb5A853875f9eC60275CF498"
|
||||
],
|
||||
"uniswap_pool_fees": [
|
||||
3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 500, 500, 500, 500, 500,
|
||||
500, 500
|
||||
],
|
||||
"pool_tokens": [
|
||||
"eth",
|
||||
"eth",
|
||||
"eth",
|
||||
"eth",
|
||||
"dai",
|
||||
"dai",
|
||||
"dai",
|
||||
"dai",
|
||||
"cdai",
|
||||
"cdai",
|
||||
"cdai",
|
||||
"cdai",
|
||||
"usdc",
|
||||
"usdc",
|
||||
"usdt",
|
||||
"usdt",
|
||||
"wbtc",
|
||||
"wbtc",
|
||||
"wbtc"
|
||||
],
|
||||
"pool_denominations": [
|
||||
"0.1",
|
||||
"1",
|
||||
"10",
|
||||
"100",
|
||||
"100",
|
||||
"1000",
|
||||
"10000",
|
||||
"100000",
|
||||
"5000",
|
||||
"50000",
|
||||
"500000",
|
||||
"5000000",
|
||||
"100",
|
||||
"1000",
|
||||
"100",
|
||||
"1000",
|
||||
"0.1",
|
||||
"1",
|
||||
"10"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/* global ethers, network */
|
||||
|
||||
async function setTime(timestamp) {
|
||||
await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp])
|
||||
}
|
||||
|
||||
async function takeSnapshot() {
|
||||
return await ethers.provider.send('evm_snapshot', [])
|
||||
}
|
||||
|
||||
async function revertSnapshot(id) {
|
||||
await ethers.provider.send('evm_revert', [id])
|
||||
}
|
||||
|
||||
async function advanceTime(sec) {
|
||||
const now = (await ethers.provider.getBlock('latest')).timestamp
|
||||
await setTime(now + sec)
|
||||
}
|
||||
|
||||
async function getSignerFromAddress(address) {
|
||||
await network.provider.request({
|
||||
method: 'hardhat_impersonateAccount',
|
||||
params: [address],
|
||||
})
|
||||
|
||||
let signer = await ethers.provider.getSigner(address)
|
||||
signer.address = signer._address
|
||||
return signer
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setTime,
|
||||
advanceTime,
|
||||
takeSnapshot,
|
||||
revertSnapshot,
|
||||
getSignerFromAddress,
|
||||
}
|
|
@ -3,6 +3,7 @@ const { expect } = require('chai')
|
|||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const { PermitSigner } = require('../../scripts/v1/Permit.js')
|
||||
const tornConfig = require('torn-token')
|
||||
const config = require('../../config')
|
||||
|
||||
const ProposalState = {
|
||||
Pending: 0,
|
||||
|
@ -35,7 +36,7 @@ const duration = {
|
|||
},
|
||||
}
|
||||
|
||||
describe('Governance tests', () => {
|
||||
describe('V1 governance tests', () => {
|
||||
/// NETWORK && DOMAIN
|
||||
let chainId
|
||||
let domain
|
||||
|
@ -934,7 +935,7 @@ describe('Governance tests', () => {
|
|||
{
|
||||
forking: {
|
||||
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : 13042331,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : config.forkBlockNumber,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
|
|
@ -1,743 +0,0 @@
|
|||
const { expect } = require('chai')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const { propose } = require('../../scripts/helper/propose_proposal.js')
|
||||
const testcases = require('@ethersproject/testcases')
|
||||
const seedbase = require('../../resources/hdnode.json')
|
||||
const accountList = require('../../resources/accounts.json')
|
||||
const EasyAuctionJson = require('@gnosis.pm/ido-contracts/build/artifacts/contracts/EasyAuction.sol/EasyAuction.json')
|
||||
|
||||
describe('Start of tests', () => {
|
||||
///// ON-CHAIN CONSTANTS
|
||||
let proxy_address = '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce'
|
||||
let quorumVotes
|
||||
|
||||
///////////////////////////// CONTRACTS
|
||||
let GovernanceContract
|
||||
let TornToken
|
||||
let WETH
|
||||
let TornadoAuctionHandler
|
||||
let GnosisEasyAuction
|
||||
|
||||
let ProposalFactory
|
||||
let ProposalContract
|
||||
|
||||
let GasCompensationFactory
|
||||
let GasCompensationContract
|
||||
|
||||
let OrderHelperFactory
|
||||
let OrderHelper
|
||||
|
||||
//////////////////// IMPERSONATED
|
||||
let tornadoMultisig
|
||||
|
||||
//////////////////////////////// MOCK
|
||||
let MockProposalFactory
|
||||
|
||||
/////// GOV PARAMS
|
||||
const ProposalState = {
|
||||
Pending: 0,
|
||||
Active: 1,
|
||||
Defeated: 2,
|
||||
Timelocked: 3,
|
||||
AwaitingExecution: 4,
|
||||
Executed: 5,
|
||||
Expired: 6,
|
||||
}
|
||||
|
||||
///// ACCOUNTS
|
||||
let dore
|
||||
let whale
|
||||
let signerArray = []
|
||||
let whales = []
|
||||
|
||||
//////////////////////////////////// TESTING & UTILITY
|
||||
let randN = Math.floor(Math.random() * 1023)
|
||||
let testseed = seedbase[randN].seed
|
||||
|
||||
let minewait = async (time) => {
|
||||
await ethers.provider.send('evm_increaseTime', [time])
|
||||
await ethers.provider.send('evm_mine', [])
|
||||
}
|
||||
|
||||
let timestamp = async () => {
|
||||
return (await ethers.provider.getBlock('latest')).timestamp
|
||||
}
|
||||
|
||||
let sendr = async (method, params) => {
|
||||
return await ethers.provider.send(method, params)
|
||||
}
|
||||
|
||||
let clog = (...x) => {
|
||||
console.log(x)
|
||||
}
|
||||
|
||||
let pE = (x) => {
|
||||
return ethers.utils.parseEther(`${x}`)
|
||||
}
|
||||
|
||||
let rand = (l, u) => {
|
||||
return testcases.randomNumber(testseed, l, u)
|
||||
}
|
||||
|
||||
let snapshotIdArray = []
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////7
|
||||
before(async function () {
|
||||
signerArray = await ethers.getSigners()
|
||||
dore = signerArray[0]
|
||||
|
||||
GasCompensationFactory = await ethers.getContractFactory(
|
||||
'contracts/v2-vault-and-gas/testing/GasCompensationVault.sol:GasCompensationVault',
|
||||
)
|
||||
GasCompensationContract = await GasCompensationFactory.deploy()
|
||||
|
||||
MockProposalFactory = await ethers.getContractFactory('MockProposal1')
|
||||
|
||||
ProposalFactory = await ethers.getContractFactory('VaultAndGasProposal')
|
||||
|
||||
ProposalContract = await ProposalFactory.deploy(GasCompensationContract.address, 260000)
|
||||
|
||||
OrderHelperFactory = await ethers.getContractFactory('OrderEncoderHelper')
|
||||
OrderHelper = await OrderHelperFactory.deploy()
|
||||
|
||||
GovernanceContract = await ethers.getContractAt('contracts/v1/Governance.sol:Governance', proxy_address)
|
||||
GnosisEasyAuction = await ethers.getContractAt(
|
||||
EasyAuctionJson.abi,
|
||||
'0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101',
|
||||
)
|
||||
|
||||
TornToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
'0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
)
|
||||
WETH = await ethers.getContractAt('IWETH', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
|
||||
|
||||
quorumVotes = await GovernanceContract.QUORUM_VOTES()
|
||||
})
|
||||
|
||||
describe('Test complete functionality', () => {
|
||||
describe('Imitation block', () => {
|
||||
it('Should successfully imitate tornado multisig', async function () {
|
||||
await sendr('hardhat_impersonateAccount', ['0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4'])
|
||||
tornadoMultisig = await ethers.getSigner('0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4')
|
||||
})
|
||||
|
||||
it('Should successfully imitate whale', async function () {
|
||||
await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3'])
|
||||
whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3')
|
||||
GovernanceContract = await GovernanceContract.connect(whale)
|
||||
|
||||
let balance = await TornToken.balanceOf(whale.address)
|
||||
TornToken = await TornToken.connect(whale)
|
||||
|
||||
await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000'))
|
||||
await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted
|
||||
|
||||
expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(
|
||||
balance.toString(),
|
||||
)
|
||||
snapshotIdArray[0] = await sendr('evm_snapshot', [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Proposal passing block', () => {
|
||||
it('Should successfully pass the proposal', async function () {
|
||||
let response, id, state
|
||||
;[response, id, state] = await propose([whale, ProposalContract, 'Gas Upgrade'])
|
||||
|
||||
const { events } = await response.wait()
|
||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.proposer).to.be.equal(whale.address)
|
||||
expect(args.target).to.be.equal(ProposalContract.address)
|
||||
expect(args.description).to.be.equal('Gas Upgrade')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber())
|
||||
await expect(GovernanceContract.castVote(id, true)).to.not.be.reverted
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
await minewait(
|
||||
(
|
||||
await GovernanceContract.VOTING_PERIOD()
|
||||
)
|
||||
.add(await GovernanceContract.EXECUTION_DELAY())
|
||||
.add(86400)
|
||||
.toNumber(),
|
||||
)
|
||||
|
||||
await dore.sendTransaction({ to: whale.address, value: pE(10) })
|
||||
const executeResponse = await GovernanceContract.execute(id)
|
||||
const executeReceipt = await executeResponse.wait()
|
||||
|
||||
console.log(
|
||||
'______________________\n',
|
||||
'Gas used for execution: ',
|
||||
executeReceipt.cumulativeGasUsed.toString(),
|
||||
'\n-------------------------\n',
|
||||
)
|
||||
const topic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
||||
let handlerAddress
|
||||
|
||||
for (let i = 0; i < executeReceipt.logs.length; i++) {
|
||||
if (executeReceipt.logs[i].topics[0] == topic) {
|
||||
handlerAddress = executeReceipt.logs[i].topics[1]
|
||||
}
|
||||
}
|
||||
|
||||
TornadoAuctionHandler = await ethers.getContractAt(
|
||||
'TornadoAuctionHandler',
|
||||
'0x' + handlerAddress.slice(26),
|
||||
)
|
||||
GovernanceContract = await ethers.getContractAt('GovernanceGasUpgrade', GovernanceContract.address)
|
||||
|
||||
clog(await GovernanceContract.version())
|
||||
const auctionCounter = 38
|
||||
const auctionData = await GnosisEasyAuction.auctionData(auctionCounter)
|
||||
expect(auctionData.auctioningToken).to.equal(TornToken.address)
|
||||
|
||||
console.log(
|
||||
'////////////////AUCTION/////////////////\n',
|
||||
'Started at: ',
|
||||
await timestamp(),
|
||||
', Will end at: ',
|
||||
auctionData.auctionEndDate.toString(),
|
||||
'\n////////////////////////////////',
|
||||
)
|
||||
|
||||
snapshotIdArray[1] = await sendr('evm_snapshot', [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Mock rewards + proposal distribution with multiple accounts', () => {
|
||||
let addrArray = []
|
||||
let signerArmy = []
|
||||
let delegatedSignerArmy = []
|
||||
let votingAddressArray = []
|
||||
const numberOfVoters = 80
|
||||
const numberOfDelegators = 30
|
||||
|
||||
it('Should create empty address array', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
votingAddressArray[i] = new Array(numberOfDelegators / 10 + 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should impersonate and fund 80 accounts', async function () {
|
||||
////////// WRITE WHALE ADDRESSES AND PREPARE FOR TRANSFERS
|
||||
addrArray = [
|
||||
'0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b',
|
||||
'0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||
'0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3',
|
||||
'0x055AD5E56c11c0eF55818155c69ed9BA2f4b3e90',
|
||||
]
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await sendr('hardhat_impersonateAccount', [addrArray[i]])
|
||||
whales[i] = await ethers.getSigner(addrArray[i])
|
||||
}
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
//last test really unnecessary
|
||||
const torn = await TornToken.connect(whales[i])
|
||||
const whaleBalance = await torn.balanceOf(whales[i].address)
|
||||
await torn.approve(addrArray[0], whaleBalance)
|
||||
await expect(() => torn.transfer(addrArray[0], whaleBalance)).to.changeTokenBalance(
|
||||
torn,
|
||||
whales[0],
|
||||
whaleBalance,
|
||||
)
|
||||
}
|
||||
|
||||
const whale0Balance = await TornToken.balanceOf(whales[0].address)
|
||||
const toTransfer = whale0Balance.sub(pE(10000)).div(numberOfVoters * 3)
|
||||
let torn0 = await TornToken.connect(whales[0])
|
||||
const oldBalance = await TornToken.balanceOf(await GovernanceContract.userVault())
|
||||
let lockedSum = BigNumber.from(0)
|
||||
|
||||
////////// TRANSFER TO 50 ACCOUNTS + DELEGATION TO 10
|
||||
|
||||
for (let i = 0; i < numberOfVoters; i++) {
|
||||
/// PREPARE ACCOUNTS
|
||||
const accAddress = accountList[i + 7].checksumAddress
|
||||
await sendr('hardhat_impersonateAccount', [accAddress])
|
||||
|
||||
signerArmy[i] = await ethers.getSigner(accAddress)
|
||||
const tx = { to: signerArmy[i].address, value: pE(1) }
|
||||
|
||||
await signerArray[0].sendTransaction(tx)
|
||||
|
||||
/// FILL WITH GAS FOR LATER
|
||||
await expect(() => torn0.transfer(signerArmy[i].address, toTransfer)).to.changeTokenBalance(
|
||||
torn0,
|
||||
signerArmy[i],
|
||||
toTransfer,
|
||||
)
|
||||
let torn = await torn0.connect(signerArmy[i])
|
||||
|
||||
/// APPROVE TO GOVERNANCE FOR LOCK
|
||||
await expect(torn.approve(GovernanceContract.address, toTransfer)).to.not.be.reverted
|
||||
const gov = await GovernanceContract.connect(signerArmy[i])
|
||||
|
||||
///// LOCK
|
||||
if (i > numberOfVoters / 2) {
|
||||
await expect(() => gov.lockWithApproval(toTransfer.div(i))).to.changeTokenBalance(
|
||||
torn,
|
||||
signerArmy[i],
|
||||
BigNumber.from(0).sub(toTransfer.div(i)),
|
||||
)
|
||||
lockedSum = lockedSum.add(toTransfer.div(i))
|
||||
} else {
|
||||
await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance(
|
||||
torn,
|
||||
signerArmy[i],
|
||||
BigNumber.from(0).sub(toTransfer),
|
||||
)
|
||||
lockedSum = lockedSum.add(toTransfer)
|
||||
}
|
||||
|
||||
if (i > numberOfVoters - numberOfDelegators - 1) {
|
||||
delegatedSignerArmy[i - (numberOfVoters - numberOfDelegators)] = signerArmy[i]
|
||||
}
|
||||
|
||||
if (i < 10) {
|
||||
votingAddressArray[i][0] = signerArmy[i].address
|
||||
}
|
||||
|
||||
const restBalance = await torn.balanceOf(signerArmy[i].address)
|
||||
await torn.transfer(whale.address, restBalance)
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberOfDelegators; i++) {
|
||||
const gov = await GovernanceContract.connect(delegatedSignerArmy[i])
|
||||
/// DELEGATE TO 10 FIRST SIGNERS
|
||||
await expect(gov.delegate(signerArmy[i % 10].address)).to.emit(gov, 'Delegated')
|
||||
votingAddressArray[i % 10][Math.floor(i / 10) + 1] = delegatedSignerArmy[i].address
|
||||
}
|
||||
|
||||
const TornVault = await GovernanceContract.userVault()
|
||||
expect(await TornToken.balanceOf(TornVault)).to.equal(lockedSum.add(oldBalance))
|
||||
|
||||
const gov = await GovernanceContract.connect(whales[0])
|
||||
await expect(torn0.approve(GovernanceContract.address, pE(10000))).to.not.be.reverted
|
||||
await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance(
|
||||
torn0,
|
||||
whales[0],
|
||||
BigNumber.from(0).sub(toTransfer),
|
||||
)
|
||||
|
||||
snapshotIdArray[2] = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Should test if auction handler can convert ETH to gov', async function () {
|
||||
WETH = await WETH.connect(signerArray[4])
|
||||
await WETH.deposit({ value: pE(100) })
|
||||
await WETH.transfer(TornadoAuctionHandler.address, pE(100))
|
||||
await expect(() => TornadoAuctionHandler.convertAndTransferToGovernance()).to.changeEtherBalance(
|
||||
GovernanceContract,
|
||||
pE(100),
|
||||
)
|
||||
})
|
||||
|
||||
it('Should test if auction will behave properly', async function () {
|
||||
snapshotIdArray[2] = await sendr('evm_snapshot', [])
|
||||
|
||||
let orderArray = []
|
||||
const initialHandlerBalance = await WETH.balanceOf(TornadoAuctionHandler.address)
|
||||
|
||||
/**
|
||||
* 100 TORN in to total
|
||||
* Price as of time of writing 1 ETH == 51.66 TORN
|
||||
* First test is an overbought auction, 20 buyers compete for 40 TORN
|
||||
*/
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
const bidder = signerArray[i]
|
||||
|
||||
WETH = await WETH.connect(bidder)
|
||||
await expect(() => WETH.deposit({ value: pE(100) })).to.changeEtherBalance(
|
||||
bidder,
|
||||
BigNumber.from(0).sub(pE(100)),
|
||||
)
|
||||
|
||||
const buyAmount = pE(240)
|
||||
const sellAmount = pE(23.73 + i / 100)
|
||||
|
||||
await WETH.approve(GnosisEasyAuction.address, sellAmount)
|
||||
|
||||
GnosisEasyAuction = await GnosisEasyAuction.connect(bidder)
|
||||
|
||||
await GnosisEasyAuction.placeSellOrders(
|
||||
38,
|
||||
[buyAmount],
|
||||
[sellAmount],
|
||||
['0x0000000000000000000000000000000000000000000000000000000000000001'],
|
||||
'0x',
|
||||
)
|
||||
|
||||
orderArray[i] = await OrderHelper.encodeOrder(
|
||||
await GnosisEasyAuction.numUsers(),
|
||||
buyAmount,
|
||||
sellAmount,
|
||||
)
|
||||
}
|
||||
|
||||
let auctionEndDt = (await GnosisEasyAuction.auctionData(38))[3].sub(BigNumber.from(await timestamp()))
|
||||
|
||||
await minewait(auctionEndDt.toNumber())
|
||||
|
||||
await GnosisEasyAuction.settleAuction(38)
|
||||
|
||||
expect(await WETH.balanceOf(TornadoAuctionHandler.address)).to.be.gt(initialHandlerBalance)
|
||||
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
await GnosisEasyAuction.claimFromParticipantOrder(38, [orderArray[i]])
|
||||
const balance = await TornToken.balanceOf(signerArray[i].address)
|
||||
if (balance.toString() != '0') console.log(`Signer ${i} claimed:`, balance.toString(), ' torn')
|
||||
}
|
||||
console.log('All other signers got nothing!')
|
||||
|
||||
let claimedSum = BigNumber.from(0)
|
||||
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
const claimed = await TornToken.balanceOf(signerArray[i].address)
|
||||
claimedSum = claimedSum.add(claimed)
|
||||
}
|
||||
|
||||
expect(claimedSum).to.closeTo(ethers.utils.parseEther('787'), ethers.utils.parseUnits('1', 'szabo'))
|
||||
|
||||
/// Now revert and test with lower
|
||||
await sendr('evm_revert', [snapshotIdArray[2]])
|
||||
snapshotIdArray[2] = await sendr('evm_snapshot', [])
|
||||
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
const bidder = signerArray[i]
|
||||
|
||||
WETH = await WETH.connect(bidder)
|
||||
await expect(() => WETH.deposit({ value: pE(100) })).to.changeEtherBalance(
|
||||
bidder,
|
||||
BigNumber.from(0).sub(pE(100)),
|
||||
)
|
||||
|
||||
const buyAmount = pE(0.5)
|
||||
const sellAmount = pE(22 + i / 100)
|
||||
|
||||
await WETH.approve(GnosisEasyAuction.address, sellAmount)
|
||||
|
||||
GnosisEasyAuction = await GnosisEasyAuction.connect(bidder)
|
||||
|
||||
await GnosisEasyAuction.placeSellOrders(
|
||||
38,
|
||||
[buyAmount],
|
||||
[sellAmount],
|
||||
['0x0000000000000000000000000000000000000000000000000000000000000001'],
|
||||
'0x',
|
||||
)
|
||||
|
||||
orderArray[i] = await OrderHelper.encodeOrder(
|
||||
await GnosisEasyAuction.numUsers(),
|
||||
buyAmount,
|
||||
sellAmount,
|
||||
)
|
||||
}
|
||||
|
||||
auctionEndDt = (await GnosisEasyAuction.auctionData(38))[3].sub(BigNumber.from(await timestamp()))
|
||||
|
||||
await minewait(auctionEndDt.toNumber())
|
||||
|
||||
await GnosisEasyAuction.settleAuction(38)
|
||||
|
||||
expect(await WETH.balanceOf(TornadoAuctionHandler.address)).to.be.gt(initialHandlerBalance)
|
||||
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
await GnosisEasyAuction.claimFromParticipantOrder(38, [orderArray[i]])
|
||||
console.log(
|
||||
`Signer ${i} claimed: `,
|
||||
(await TornToken.balanceOf(signerArray[i].address)).toString(),
|
||||
' torn',
|
||||
)
|
||||
}
|
||||
|
||||
claimedSum = BigNumber.from(0)
|
||||
|
||||
for (let i = 0; i < signerArray.length; i++) {
|
||||
const claimed = await TornToken.balanceOf(signerArray[i].address)
|
||||
claimedSum = claimedSum.add(claimed)
|
||||
}
|
||||
|
||||
expect(claimedSum).to.be.closeTo(
|
||||
ethers.utils.parseEther('787'),
|
||||
ethers.utils.parseUnits('1', 'szabo'),
|
||||
)
|
||||
})
|
||||
|
||||
it('Test multiple accounts proposal', async function () {
|
||||
let checkIfQuorumFulfilled = async function (proposalId) {
|
||||
const proposalData = await GovernanceContract.proposals(proposalId)
|
||||
const allVotes = proposalData[4].add(proposalData[5])
|
||||
return allVotes.gte(quorumVotes)
|
||||
}
|
||||
|
||||
ProposalContract = await MockProposalFactory.deploy()
|
||||
|
||||
clog(
|
||||
'Torn balance of governance contract: ',
|
||||
(await TornToken.balanceOf(GovernanceContract.address)).toString(),
|
||||
)
|
||||
|
||||
////////////// STANDARD PROPOSAL ARGS TEST //////////////////////
|
||||
let response, id, state
|
||||
;[response, id, state] = await propose([whales[0], ProposalContract, 'LotteryUpgrade'])
|
||||
const { events } = await response.wait()
|
||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.target).to.be.equal(ProposalContract.address)
|
||||
expect(args.description).to.be.equal('LotteryUpgrade')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
////////////////////////INCREMENT TO VOTING TIME////////////////////////
|
||||
await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber())
|
||||
|
||||
/////////////////// PREPARE MULTISIG AND COMPENSATIONS
|
||||
let multiGov = await GovernanceContract.connect(tornadoMultisig)
|
||||
|
||||
await dore.sendTransaction({ to: tornadoMultisig.address, value: pE(1) })
|
||||
await expect(multiGov.setGasCompensations(pE(500))).to.not.be.reverted
|
||||
///////////////////////////// VOTE ////////////////////////////
|
||||
const overrides = {
|
||||
gasPrice: BigNumber.from(5),
|
||||
}
|
||||
|
||||
let signerArmyBalanceInitial = []
|
||||
let signerArmyBalanceDiff = []
|
||||
let gasUsedArray = []
|
||||
|
||||
snapshotIdArray[3] = await sendr('evm_snapshot', [])
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
signerArmyBalanceInitial[i] = await signerArmy[i].getBalance()
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides)
|
||||
}
|
||||
|
||||
signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id))
|
||||
? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance())
|
||||
: signerArmyBalanceDiff[i - 1]
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedArray[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
signerArmyBalanceInitial[i] = await signerArmy[i].getBalance()
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castVote(id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castVote(id, false, overrides)
|
||||
}
|
||||
|
||||
signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id))
|
||||
? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance())
|
||||
: signerArmyBalanceDiff[i - 1]
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedArray[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
//////////////////////////////// GET STATE ///////////////////////////////
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
|
||||
///////////////////////////// VOTER INFO ///////////////////////////////////
|
||||
// (uncomment for more data)
|
||||
/*
|
||||
for (i = 0; i < numberOfVoters; i+=5) {
|
||||
const j = BigNumber.from(i);
|
||||
console.log(
|
||||
`Voter ${i} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j))[0]).toString(),
|
||||
`Voter ${i+1} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(1)))[0]).toString(),
|
||||
`Voter ${i+2} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(2)))[0]).toString(),
|
||||
`Voter ${i+3} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(3)))[0]).toString(),
|
||||
`Voter ${i+4} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(4)))[0]).toString(),
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
|
||||
for (i = 0; i < numberOfVoters; i+=5) {
|
||||
console.log(
|
||||
`Voter ${i} ether used: `,
|
||||
gasUsedArray[i],
|
||||
`Voter ${i+1} ether used: `,
|
||||
gasUsedArray[i+1],
|
||||
`Voter ${i+2} ether used: `,
|
||||
gasUsedArray[i+2],
|
||||
`Voter ${i+3} ether used: `,
|
||||
gasUsedArray[i+3],
|
||||
`Voter ${i+4} ether used: `,
|
||||
gasUsedArray[i+4],
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
await sendr('evm_revert', [snapshotIdArray[3]])
|
||||
|
||||
///////////////////////////////// VOTE WITHOUT COMPENSATION //////////////////////////////////////
|
||||
let gasUsedWithoutCompensation = []
|
||||
await multiGov.setGasCompensations(pE(100000))
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides)
|
||||
}
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castVote(id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castVote(id, false, overrides)
|
||||
}
|
||||
|
||||
const receipt = await response.wait()
|
||||
|
||||
gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
await multiGov.setGasCompensations(pE(100))
|
||||
//////////////////////////////// GET STATE ///////////////////////////////
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
|
||||
///////////////////////////// VOTING GAS INFO ///////////////////////////////////
|
||||
let gasUsedSumNoComp = BigNumber.from(0)
|
||||
let gasUsedSum = BigNumber.from(0)
|
||||
let gasSumDiff = BigNumber.from(0)
|
||||
let gasUsedSumNoCompDel = BigNumber.from(0)
|
||||
let gasUsedSumDel = BigNumber.from(0)
|
||||
let gasSumDiffDel = BigNumber.from(0)
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
gasUsedSumDel = gasUsedSumDel.add(gasUsedArray[i])
|
||||
gasUsedSumNoCompDel = gasUsedSumNoCompDel.add(gasUsedWithoutCompensation[i])
|
||||
gasSumDiffDel = gasSumDiffDel.add(signerArmyBalanceDiff[i])
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
gasUsedSum = gasUsedSum.add(gasUsedArray[i])
|
||||
gasUsedSumNoComp = gasUsedSumNoComp.add(gasUsedWithoutCompensation[i])
|
||||
gasSumDiff = gasSumDiff.add(signerArmyBalanceDiff[i])
|
||||
}
|
||||
|
||||
const gasUsedAverageNoCompDel = gasUsedSumNoCompDel.div(10)
|
||||
const gasUsedAverageDel = gasUsedSumDel.div(10)
|
||||
const gasSumAverageDiffDel = gasSumDiffDel.div(10)
|
||||
|
||||
const gasUsedAverageNoComp = gasUsedSumNoComp.div(numberOfVoters - 10)
|
||||
const gasUsedAverage = gasUsedSum.div(numberOfVoters - 10)
|
||||
const gasSumAverageDiff = gasSumDiff.div(numberOfVoters - 10)
|
||||
|
||||
console.log(
|
||||
'\n',
|
||||
'----------------------------CAST VOTE INFO------------------------',
|
||||
'\n',
|
||||
'Gas use average: ',
|
||||
gasUsedAverage.toString(),
|
||||
'\n',
|
||||
'Gas use without compensation average: ',
|
||||
gasUsedAverageNoComp.toString(),
|
||||
'\n',
|
||||
'Gas diff average: ',
|
||||
gasSumAverageDiff.toString(),
|
||||
'\n',
|
||||
'Gas compensated in average: ',
|
||||
gasUsedAverage.sub(gasSumAverageDiff).toString(),
|
||||
'\n',
|
||||
'--------------------------------------------------------------------',
|
||||
'\n',
|
||||
)
|
||||
|
||||
console.log(
|
||||
'\n',
|
||||
'----------------------------CAST DELEGATED VOTE INFO------------------------',
|
||||
'\n',
|
||||
'Gas use average: ',
|
||||
gasUsedAverageDel.toString(),
|
||||
'\n',
|
||||
'Gas use without compensation average: ',
|
||||
gasUsedAverageNoCompDel.toString(),
|
||||
'\n',
|
||||
'Gas diff average: ',
|
||||
gasSumAverageDiffDel.toString(),
|
||||
'\n',
|
||||
'Gas compensated in average: ',
|
||||
gasUsedAverageDel.sub(gasSumAverageDiffDel).toString(),
|
||||
'\n',
|
||||
'--------------------------------------------------------------------',
|
||||
'\n',
|
||||
)
|
||||
/////////////////////////////// INCREMENT AGAIN //////////////////////////////////
|
||||
await minewait(
|
||||
(
|
||||
await GovernanceContract.VOTING_PERIOD()
|
||||
)
|
||||
.add(await GovernanceContract.EXECUTION_DELAY())
|
||||
.add(10000)
|
||||
.toNumber(),
|
||||
)
|
||||
|
||||
////////////// EXECUTE
|
||||
if (BigNumber.from(await GovernanceContract.state(id)).eq(ProposalState.Defeated)) {
|
||||
await expect(GovernanceContract.execute(id)).to.be.reverted
|
||||
} else {
|
||||
await expect(GovernanceContract.execute(id)).to.not.be.reverted
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await ethers.provider.send('hardhat_reset', [
|
||||
{
|
||||
forking: {
|
||||
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : 13211966,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
|
@ -0,0 +1,496 @@
|
|||
const { expect } = require('chai')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const { propose } = require('../../scripts/helper/propose_proposal.js')
|
||||
const testcases = require('@ethersproject/testcases')
|
||||
const seedbase = require('../../resources/hdnode.json')
|
||||
const accountList = require('../../resources/accounts.json')
|
||||
const config = require('../../config')
|
||||
|
||||
describe('V2 governance tests', () => {
|
||||
///// ON-CHAIN CONSTANTS
|
||||
let proxy_address = '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce'
|
||||
let quorumVotes
|
||||
|
||||
///////////////////////////// CONTRACTS
|
||||
let GovernanceContract
|
||||
let TornToken
|
||||
|
||||
//////////////////// IMPERSONATED
|
||||
let tornadoMultisig
|
||||
|
||||
//////////////////////////////// MOCK
|
||||
let MockProposalFactory
|
||||
|
||||
/////// GOV PARAMS
|
||||
const ProposalState = {
|
||||
Pending: 0,
|
||||
Active: 1,
|
||||
Defeated: 2,
|
||||
Timelocked: 3,
|
||||
AwaitingExecution: 4,
|
||||
Executed: 5,
|
||||
Expired: 6,
|
||||
}
|
||||
|
||||
///// ACCOUNTS
|
||||
let dore
|
||||
let whale
|
||||
let signerArray = []
|
||||
let whales = []
|
||||
|
||||
//////////////////////////////////// TESTING & UTILITY
|
||||
let randN = Math.floor(Math.random() * 1023)
|
||||
let testseed = seedbase[randN].seed
|
||||
|
||||
let minewait = async (time) => {
|
||||
await ethers.provider.send('evm_increaseTime', [time])
|
||||
await ethers.provider.send('evm_mine', [])
|
||||
}
|
||||
|
||||
let sendr = async (method, params) => {
|
||||
return await ethers.provider.send(method, params)
|
||||
}
|
||||
|
||||
let clog = (...x) => {
|
||||
console.log(x)
|
||||
}
|
||||
|
||||
let pE = (x) => {
|
||||
return ethers.utils.parseEther(`${x}`)
|
||||
}
|
||||
|
||||
let rand = (l, u) => {
|
||||
return testcases.randomNumber(testseed, l, u)
|
||||
}
|
||||
|
||||
let snapshotIdArray = []
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////7
|
||||
before(async function () {
|
||||
signerArray = await ethers.getSigners()
|
||||
dore = signerArray[0]
|
||||
|
||||
MockProposalFactory = await ethers.getContractFactory('MockProposal')
|
||||
|
||||
GovernanceContract = await ethers.getContractAt('GovernanceGasUpgrade', proxy_address)
|
||||
|
||||
TornToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
'0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
)
|
||||
|
||||
quorumVotes = await GovernanceContract.QUORUM_VOTES()
|
||||
})
|
||||
|
||||
describe('#imitation block', () => {
|
||||
it('Should successfully imitate tornado multisig', async function () {
|
||||
await sendr('hardhat_impersonateAccount', ['0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4'])
|
||||
tornadoMultisig = await ethers.getSigner('0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4')
|
||||
})
|
||||
|
||||
it('Should successfully imitate whale', async function () {
|
||||
await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3'])
|
||||
whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3')
|
||||
GovernanceContract = await GovernanceContract.connect(whale)
|
||||
|
||||
let balance = await TornToken.balanceOf(whale.address)
|
||||
TornToken = await TornToken.connect(whale)
|
||||
|
||||
await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000'))
|
||||
await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted
|
||||
|
||||
expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString())
|
||||
snapshotIdArray[0] = await sendr('evm_snapshot', [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#mock rewards + proposal distribution with multiple accounts', () => {
|
||||
let addrArray = []
|
||||
let signerArmy = []
|
||||
let delegatedSignerArmy = []
|
||||
let votingAddressArray = []
|
||||
const numberOfVoters = 80
|
||||
const numberOfDelegators = 30
|
||||
|
||||
it('Should create empty address array', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
votingAddressArray[i] = new Array(numberOfDelegators / 10 + 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should impersonate and fund 80 accounts', async function () {
|
||||
////////// WRITE WHALE ADDRESSES AND PREPARE FOR TRANSFERS
|
||||
addrArray = [
|
||||
'0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b',
|
||||
'0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||
'0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3',
|
||||
'0x055AD5E56c11c0eF55818155c69ed9BA2f4b3e90',
|
||||
]
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await sendr('hardhat_impersonateAccount', [addrArray[i]])
|
||||
whales[i] = await ethers.getSigner(addrArray[i])
|
||||
}
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
//last test really unnecessary
|
||||
const torn = await TornToken.connect(whales[i])
|
||||
const whaleBalance = await torn.balanceOf(whales[i].address)
|
||||
await torn.approve(addrArray[0], whaleBalance)
|
||||
await expect(() => torn.transfer(addrArray[0], whaleBalance)).to.changeTokenBalance(
|
||||
torn,
|
||||
whales[0],
|
||||
whaleBalance,
|
||||
)
|
||||
}
|
||||
|
||||
const whale0Balance = await TornToken.balanceOf(whales[0].address)
|
||||
const toTransfer = whale0Balance.sub(pE(10000)).div(numberOfVoters * 3)
|
||||
let torn0 = await TornToken.connect(whales[0])
|
||||
const oldBalance = await TornToken.balanceOf(await GovernanceContract.userVault())
|
||||
let lockedSum = BigNumber.from(0)
|
||||
|
||||
////////// TRANSFER TO 50 ACCOUNTS + DELEGATION TO 10
|
||||
|
||||
for (let i = 0; i < numberOfVoters; i++) {
|
||||
/// PREPARE ACCOUNTS
|
||||
const accAddress = accountList[i + 7].checksumAddress
|
||||
await sendr('hardhat_impersonateAccount', [accAddress])
|
||||
|
||||
signerArmy[i] = await ethers.getSigner(accAddress)
|
||||
const tx = { to: signerArmy[i].address, value: pE(1) }
|
||||
|
||||
await signerArray[0].sendTransaction(tx)
|
||||
|
||||
/// FILL WITH GAS FOR LATER
|
||||
await expect(() => torn0.transfer(signerArmy[i].address, toTransfer)).to.changeTokenBalance(
|
||||
torn0,
|
||||
signerArmy[i],
|
||||
toTransfer,
|
||||
)
|
||||
let torn = await torn0.connect(signerArmy[i])
|
||||
|
||||
/// APPROVE TO GOVERNANCE FOR LOCK
|
||||
await expect(torn.approve(GovernanceContract.address, toTransfer)).to.not.be.reverted
|
||||
const gov = await GovernanceContract.connect(signerArmy[i])
|
||||
|
||||
///// LOCK
|
||||
if (i > numberOfVoters / 2) {
|
||||
await expect(() => gov.lockWithApproval(toTransfer.div(i))).to.changeTokenBalance(
|
||||
torn,
|
||||
signerArmy[i],
|
||||
BigNumber.from(0).sub(toTransfer.div(i)),
|
||||
)
|
||||
lockedSum = lockedSum.add(toTransfer.div(i))
|
||||
} else {
|
||||
await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance(
|
||||
torn,
|
||||
signerArmy[i],
|
||||
BigNumber.from(0).sub(toTransfer),
|
||||
)
|
||||
lockedSum = lockedSum.add(toTransfer)
|
||||
}
|
||||
|
||||
if (i > numberOfVoters - numberOfDelegators - 1) {
|
||||
delegatedSignerArmy[i - (numberOfVoters - numberOfDelegators)] = signerArmy[i]
|
||||
}
|
||||
|
||||
if (i < 10) {
|
||||
votingAddressArray[i][0] = signerArmy[i].address
|
||||
}
|
||||
|
||||
const restBalance = await torn.balanceOf(signerArmy[i].address)
|
||||
await torn.transfer(whale.address, restBalance)
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberOfDelegators; i++) {
|
||||
const gov = await GovernanceContract.connect(delegatedSignerArmy[i])
|
||||
/// DELEGATE TO 10 FIRST SIGNERS
|
||||
await expect(gov.delegate(signerArmy[i % 10].address)).to.emit(gov, 'Delegated')
|
||||
votingAddressArray[i % 10][Math.floor(i / 10) + 1] = delegatedSignerArmy[i].address
|
||||
}
|
||||
|
||||
const TornVault = await GovernanceContract.userVault()
|
||||
expect(await TornToken.balanceOf(TornVault)).to.equal(lockedSum.add(oldBalance))
|
||||
|
||||
const gov = await GovernanceContract.connect(whales[0])
|
||||
await expect(torn0.approve(GovernanceContract.address, pE(10000))).to.not.be.reverted
|
||||
await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance(
|
||||
torn0,
|
||||
whales[0],
|
||||
BigNumber.from(0).sub(toTransfer),
|
||||
)
|
||||
|
||||
snapshotIdArray[1] = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Test multiple accounts proposal', async function () {
|
||||
let checkIfQuorumFulfilled = async function (proposalId) {
|
||||
const proposalData = await GovernanceContract.proposals(proposalId)
|
||||
const allVotes = proposalData[4].add(proposalData[5])
|
||||
return allVotes.gte(quorumVotes)
|
||||
}
|
||||
|
||||
const ProposalContract = await MockProposalFactory.deploy()
|
||||
|
||||
clog(
|
||||
'Torn balance of governance contract: ',
|
||||
(await TornToken.balanceOf(GovernanceContract.address)).toString(),
|
||||
)
|
||||
|
||||
////////////// STANDARD PROPOSAL ARGS TEST //////////////////////
|
||||
let response, id, state
|
||||
;[response, id, state] = await propose([whales[0], ProposalContract, 'LotteryUpgrade'])
|
||||
const { events } = await response.wait()
|
||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.target).to.be.equal(ProposalContract.address)
|
||||
expect(args.description).to.be.equal('LotteryUpgrade')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
////////////////////////INCREMENT TO VOTING TIME////////////////////////
|
||||
await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber())
|
||||
|
||||
/////////////////// PREPARE MULTISIG AND COMPENSATIONS
|
||||
let multiGov = await GovernanceContract.connect(tornadoMultisig)
|
||||
|
||||
await dore.sendTransaction({ to: tornadoMultisig.address, value: pE(1) })
|
||||
await expect(multiGov.setGasCompensations(pE(500))).to.not.be.reverted
|
||||
///////////////////////////// VOTE ////////////////////////////
|
||||
const overrides = {
|
||||
gasPrice: BigNumber.from(5),
|
||||
}
|
||||
|
||||
let signerArmyBalanceInitial = []
|
||||
let signerArmyBalanceDiff = []
|
||||
let gasUsedArray = []
|
||||
|
||||
snapshotIdArray[2] = await sendr('evm_snapshot', [])
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
signerArmyBalanceInitial[i] = await signerArmy[i].getBalance()
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides)
|
||||
}
|
||||
|
||||
signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id))
|
||||
? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance())
|
||||
: signerArmyBalanceDiff[i - 1]
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedArray[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
signerArmyBalanceInitial[i] = await signerArmy[i].getBalance()
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castVote(id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castVote(id, false, overrides)
|
||||
}
|
||||
|
||||
signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id))
|
||||
? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance())
|
||||
: signerArmyBalanceDiff[i - 1]
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedArray[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
//////////////////////////////// GET STATE ///////////////////////////////
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
|
||||
///////////////////////////// VOTER INFO ///////////////////////////////////
|
||||
// (uncomment for more data)
|
||||
/*
|
||||
for (i = 0; i < numberOfVoters; i+=5) {
|
||||
const j = BigNumber.from(i);
|
||||
console.log(
|
||||
`Voter ${i} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j))[0]).toString(),
|
||||
`Voter ${i+1} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(1)))[0]).toString(),
|
||||
`Voter ${i+2} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(2)))[0]).toString(),
|
||||
`Voter ${i+3} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(3)))[0]).toString(),
|
||||
`Voter ${i+4} sqrt: `,
|
||||
((await GovernanceLottery.lotteryUserData(id,j.add(4)))[0]).toString(),
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
|
||||
for (i = 0; i < numberOfVoters; i+=5) {
|
||||
console.log(
|
||||
`Voter ${i} ether used: `,
|
||||
gasUsedArray[i],
|
||||
`Voter ${i+1} ether used: `,
|
||||
gasUsedArray[i+1],
|
||||
`Voter ${i+2} ether used: `,
|
||||
gasUsedArray[i+2],
|
||||
`Voter ${i+3} ether used: `,
|
||||
gasUsedArray[i+3],
|
||||
`Voter ${i+4} ether used: `,
|
||||
gasUsedArray[i+4],
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
await sendr('evm_revert', [snapshotIdArray[2]])
|
||||
|
||||
///////////////////////////////// VOTE WITHOUT COMPENSATION //////////////////////////////////////
|
||||
let gasUsedWithoutCompensation = []
|
||||
await multiGov.setGasCompensations(pE(100000))
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides)
|
||||
}
|
||||
|
||||
const receipt = await response.wait()
|
||||
gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
let gov = await GovernanceContract.connect(signerArmy[i])
|
||||
let randN = rand(i * 5, i * 6)
|
||||
randN = randN % 2
|
||||
let response
|
||||
|
||||
if (randN > 0) {
|
||||
response = await gov.castVote(id, true, overrides)
|
||||
} else {
|
||||
response = await gov.castVote(id, false, overrides)
|
||||
}
|
||||
|
||||
const receipt = await response.wait()
|
||||
|
||||
gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed
|
||||
}
|
||||
|
||||
await multiGov.setGasCompensations(pE(100))
|
||||
//////////////////////////////// GET STATE ///////////////////////////////
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
|
||||
///////////////////////////// VOTING GAS INFO ///////////////////////////////////
|
||||
let gasUsedSumNoComp = BigNumber.from(0)
|
||||
let gasUsedSum = BigNumber.from(0)
|
||||
let gasSumDiff = BigNumber.from(0)
|
||||
let gasUsedSumNoCompDel = BigNumber.from(0)
|
||||
let gasUsedSumDel = BigNumber.from(0)
|
||||
let gasSumDiffDel = BigNumber.from(0)
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
gasUsedSumDel = gasUsedSumDel.add(gasUsedArray[i])
|
||||
gasUsedSumNoCompDel = gasUsedSumNoCompDel.add(gasUsedWithoutCompensation[i])
|
||||
gasSumDiffDel = gasSumDiffDel.add(signerArmyBalanceDiff[i])
|
||||
}
|
||||
|
||||
for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) {
|
||||
gasUsedSum = gasUsedSum.add(gasUsedArray[i])
|
||||
gasUsedSumNoComp = gasUsedSumNoComp.add(gasUsedWithoutCompensation[i])
|
||||
gasSumDiff = gasSumDiff.add(signerArmyBalanceDiff[i])
|
||||
}
|
||||
|
||||
const gasUsedAverageNoCompDel = gasUsedSumNoCompDel.div(10)
|
||||
const gasUsedAverageDel = gasUsedSumDel.div(10)
|
||||
const gasSumAverageDiffDel = gasSumDiffDel.div(10)
|
||||
|
||||
const gasUsedAverageNoComp = gasUsedSumNoComp.div(numberOfVoters - 10)
|
||||
const gasUsedAverage = gasUsedSum.div(numberOfVoters - 10)
|
||||
const gasSumAverageDiff = gasSumDiff.div(numberOfVoters - 10)
|
||||
|
||||
console.log(
|
||||
'\n',
|
||||
'----------------------------CAST VOTE INFO------------------------',
|
||||
'\n',
|
||||
'Gas use average: ',
|
||||
gasUsedAverage.toString(),
|
||||
'\n',
|
||||
'Gas use without compensation average: ',
|
||||
gasUsedAverageNoComp.toString(),
|
||||
'\n',
|
||||
'Gas diff average: ',
|
||||
gasSumAverageDiff.toString(),
|
||||
'\n',
|
||||
'Gas compensated in average: ',
|
||||
gasUsedAverage.sub(gasSumAverageDiff).toString(),
|
||||
'\n',
|
||||
'--------------------------------------------------------------------',
|
||||
'\n',
|
||||
)
|
||||
|
||||
console.log(
|
||||
'\n',
|
||||
'----------------------------CAST DELEGATED VOTE INFO------------------------',
|
||||
'\n',
|
||||
'Gas use average: ',
|
||||
gasUsedAverageDel.toString(),
|
||||
'\n',
|
||||
'Gas use without compensation average: ',
|
||||
gasUsedAverageNoCompDel.toString(),
|
||||
'\n',
|
||||
'Gas diff average: ',
|
||||
gasSumAverageDiffDel.toString(),
|
||||
'\n',
|
||||
'Gas compensated in average: ',
|
||||
gasUsedAverageDel.sub(gasSumAverageDiffDel).toString(),
|
||||
'\n',
|
||||
'--------------------------------------------------------------------',
|
||||
'\n',
|
||||
)
|
||||
/////////////////////////////// INCREMENT AGAIN //////////////////////////////////
|
||||
await minewait(
|
||||
(
|
||||
await GovernanceContract.VOTING_PERIOD()
|
||||
)
|
||||
.add(await GovernanceContract.EXECUTION_DELAY())
|
||||
.add(10000)
|
||||
.toNumber(),
|
||||
)
|
||||
|
||||
////////////// EXECUTE
|
||||
if (BigNumber.from(await GovernanceContract.state(id)).eq(ProposalState.Defeated)) {
|
||||
await expect(GovernanceContract.execute(id)).to.be.reverted
|
||||
} else {
|
||||
await expect(GovernanceContract.execute(id)).to.not.be.reverted
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await ethers.provider.send('hardhat_reset', [
|
||||
{
|
||||
forking: {
|
||||
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : config.forkBlockNumber,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
|
@ -0,0 +1,71 @@
|
|||
const { ethers } = require('hardhat')
|
||||
const { expect } = require('chai')
|
||||
|
||||
const config = require('../../config')
|
||||
const { getSignerFromAddress, takeSnapshot, revertSnapshot } = require('../utils')
|
||||
|
||||
describe('V3 governance tests', () => {
|
||||
let snapshotId
|
||||
|
||||
//// CONTRACTS
|
||||
let torn = config.TORN
|
||||
let gov
|
||||
|
||||
//// IMPERSONATED ACCOUNTS
|
||||
let tornWhale
|
||||
|
||||
//// HELPER FN
|
||||
let getToken = async (tokenAddress) => {
|
||||
return await ethers.getContractAt('@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', tokenAddress)
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
tornWhale = await getSignerFromAddress(config.tornWhale)
|
||||
|
||||
gov = (await ethers.getContractAt('GovernanceStakingUpgrade', config.governance)).connect(tornWhale)
|
||||
|
||||
snapshotId = await takeSnapshot()
|
||||
})
|
||||
|
||||
describe('#lock functionality', () => {
|
||||
it('should be able to lock/unlock torn in governance', async () => {
|
||||
const [sender] = await ethers.getSigners()
|
||||
const value = ethers.utils.parseEther('1000')
|
||||
|
||||
const tornToken = await (await getToken(torn)).connect(tornWhale)
|
||||
await tornToken.transfer(sender.address, value)
|
||||
await tornToken.connect(sender).approve(gov.address, value)
|
||||
|
||||
const ethBalanceBeforeLock = await ethers.provider.getBalance(sender.address)
|
||||
const tokenBalanceBeforeLock = await tornToken.balanceOf(sender.address)
|
||||
let tx = await gov.connect(sender).lockWithApproval(value)
|
||||
|
||||
let receipt = await tx.wait()
|
||||
let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice)
|
||||
const ethBalanceAfterLock = await ethers.provider.getBalance(sender.address)
|
||||
const tokenBalanceAfterLock = await tornToken.balanceOf(sender.address)
|
||||
expect(ethBalanceAfterLock).to.be.equal(ethBalanceBeforeLock.sub(txFee))
|
||||
expect(tokenBalanceAfterLock).to.be.equal(tokenBalanceBeforeLock.sub(value))
|
||||
|
||||
const lockedBalanceAfterLock = await gov.lockedBalance(sender.address)
|
||||
expect(lockedBalanceAfterLock).to.be.equal(value)
|
||||
|
||||
tx = await gov.connect(sender).unlock(value)
|
||||
|
||||
receipt = await tx.wait()
|
||||
txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice)
|
||||
const ethBalanceAfterUnlock = await ethers.provider.getBalance(sender.address)
|
||||
const tokenBalanceAfterUnlock = await tornToken.balanceOf(sender.address)
|
||||
expect(ethBalanceAfterUnlock).to.be.equal(ethBalanceAfterLock.sub(txFee))
|
||||
expect(tokenBalanceAfterUnlock).to.be.equal(tokenBalanceBeforeLock)
|
||||
|
||||
const lockedBalanceAfterUnlock = await gov.lockedBalance(sender.address)
|
||||
expect(lockedBalanceAfterUnlock).to.be.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await revertSnapshot(snapshotId)
|
||||
snapshotId = await takeSnapshot()
|
||||
})
|
||||
})
|
|
@ -1,35 +0,0 @@
|
|||
module.exports = {
|
||||
networks: {
|
||||
// development: {
|
||||
// // host: '127.0.0.1', // Localhost (default: none)
|
||||
// // port: 8545, // Standard Ethereum port (default: none)
|
||||
// network_id: '*', // Any network (default: none)
|
||||
// accounts: 20,
|
||||
// },
|
||||
coverage: {
|
||||
host: 'localhost',
|
||||
network_id: '*',
|
||||
port: 8554, // <-- If you change this, also set the port option in .solcover.js.
|
||||
gas: 0xfffffffffff, // <-- Use this high gas value
|
||||
gasPrice: 0x01, // <-- Use this low gas price
|
||||
},
|
||||
},
|
||||
|
||||
mocha: {
|
||||
// timeout: 100000
|
||||
},
|
||||
|
||||
compilers: {
|
||||
solc: {
|
||||
version: '0.6.12',
|
||||
docker: false,
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
// evmVersion: "byzantium"
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
5599
yarn-error.log
5599
yarn-error.log
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue