Initial commit

This commit is contained in:
Tornado Contrib 2024-03-29 21:52:45 +00:00
commit 36798e04f2
No known key found for this signature in database
GPG Key ID: 60B4DF1A076C64B1
89 changed files with 9865 additions and 0 deletions

49
.eslintrc.js Normal file
View File

@ -0,0 +1,49 @@
module.exports = {
"env": {
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
node_modules
.env
# Hardhat files
/cache
/artifacts
# TypeChain files
/typechain
/typechain-types
# solidity-coverage files
/coverage
/coverage.json

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# Tornado Cash Classic & Governance Contracts
This repository contains the current deployments of Tornado Cash Classic & Tornado Cash DAO Governance Contracts that are currently being used (Doesn't include contracts related with anonymity mining or Nova yet).
Try running some of the following tasks:
```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
```

View File

@ -0,0 +1,58 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IVerifier, IHasher, Tornado } from "./Tornado.sol";
import { IERC20, SafeERC20 } from "./libraries/SafeERC20.sol";
contract ERC20Tornado is Tornado {
using SafeERC20 for IERC20;
IERC20 public immutable token;
constructor(
IVerifier _verifier,
IHasher _hasher,
uint256 _denomination,
uint32 _merkleTreeHeight,
IERC20 _token
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {
token = _token;
}
function _processDeposit() internal override {
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance");
token.safeTransferFrom(msg.sender, address(this), denomination);
}
function _processWithdraw(
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) internal override {
require(msg.value == _refund, "Incorrect refund amount received by the contract");
token.safeTransfer(_recipient, denomination - _fee);
if (_fee > 0) {
token.safeTransfer(_relayer, _fee);
}
if (_refund > 0) {
(bool success, ) = _recipient.call{ value: _refund }("");
if (!success) {
// let's return _refund back to the relayer
_relayer.transfer(_refund);
}
}
}
}

View File

@ -0,0 +1,46 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IVerifier, IHasher, Tornado } from "./Tornado.sol";
contract ETHTornado is Tornado {
constructor(
IVerifier _verifier,
IHasher _hasher,
uint256 _denomination,
uint32 _merkleTreeHeight
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {}
function _processDeposit() internal override {
require(msg.value == denomination, "Please send `mixDenomination` ETH along with transaction");
}
function _processWithdraw(
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) internal override {
// sanity checks
require(msg.value == 0, "Message value is supposed to be zero for ETH instance");
require(_refund == 0, "Refund value is supposed to be zero for ETH instance");
(bool success, ) = _recipient.call{ value: denomination - _fee }("");
require(success, "payment to _recipient did not go thru");
if (_fee > 0) {
(success, ) = _relayer.call{ value: _fee }("");
require(success, "payment to _relayer did not go thru");
}
}
}

View File

@ -0,0 +1,127 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IHasher {
function MiMCSponge(uint256 in_xL, uint256 in_xR) external pure returns (uint256 xL, uint256 xR);
}
contract MerkleTreeWithHistory {
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
IHasher public immutable hasher;
uint32 public immutable levels;
// the following variables are made public for easier testing and debugging and
// are not supposed to be accessed in regular code
// filledSubtrees, zeros, and roots could be bytes32[size], but using mappings makes it cheaper because
// it removes index range check on every interaction
mapping(uint256 => bytes32) public filledSubtrees;
mapping(uint256 => bytes32) public zeros;
mapping(uint256 => bytes32) public roots;
uint32 public constant ROOT_HISTORY_SIZE = 30;
uint32 public currentRootIndex = 0;
uint32 public nextIndex = 0;
constructor(uint32 _levels, IHasher _hasher) {
require(_levels > 0, "_levels should be greater than zero");
require(_levels < 32, "_levels should be less than 32");
levels = _levels;
hasher = _hasher;
bytes32 currentZero = bytes32(ZERO_VALUE);
for (uint32 i = 0; i < _levels; i++) {
zeros[i] = currentZero;
filledSubtrees[i] = currentZero;
currentZero = hashLeftRight(_hasher, currentZero, currentZero);
}
roots[0] = currentZero;
}
/**
@dev Hash 2 tree leaves, returns MiMC(_left, _right)
*/
function hashLeftRight(
IHasher _hasher,
bytes32 _left,
bytes32 _right
) public pure returns (bytes32) {
require(uint256(_left) < FIELD_SIZE, "_left should be inside the field");
require(uint256(_right) < FIELD_SIZE, "_right should be inside the field");
uint256 R = uint256(_left);
uint256 C = 0;
(R, C) = _hasher.MiMCSponge(R, C);
R = addmod(R, uint256(_right), FIELD_SIZE);
(R, C) = _hasher.MiMCSponge(R, C);
return bytes32(R);
}
function _insert(bytes32 _leaf) internal returns (uint32 index) {
uint32 _nextIndex = nextIndex;
require(_nextIndex != uint32(2)**levels, "Merkle tree is full. No more leaves can be added");
uint32 currentIndex = _nextIndex;
bytes32 currentLevelHash = _leaf;
bytes32 left;
bytes32 right;
for (uint32 i = 0; i < levels; i++) {
if (currentIndex % 2 == 0) {
left = currentLevelHash;
right = zeros[i];
filledSubtrees[i] = currentLevelHash;
} else {
left = filledSubtrees[i];
right = currentLevelHash;
}
currentLevelHash = hashLeftRight(hasher, left, right);
currentIndex /= 2;
}
uint32 newRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
currentRootIndex = newRootIndex;
roots[newRootIndex] = currentLevelHash;
nextIndex = _nextIndex + 1;
return _nextIndex;
}
/**
@dev Whether the root is present in the root history
*/
function isKnownRoot(bytes32 _root) public view returns (bool) {
if (_root == 0) {
return false;
}
uint32 _currentRootIndex = currentRootIndex;
uint32 i = _currentRootIndex;
do {
if (_root == roots[i]) {
return true;
}
if (i == 0) {
i = ROOT_HISTORY_SIZE;
}
i--;
} while (i != _currentRootIndex);
return false;
}
/**
@dev Returns the last root
*/
function getLastRoot() public view returns (bytes32) {
return roots[currentRootIndex];
}
}

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract BadRecipient {
fallback() external {
require(false, "this contract does not accept ETH");
}
}

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20Mock is ERC20("DAIMock", "DAIM") {
function mint(address account, uint256 amount) public {
_mint(account, amount);
}
}

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IDeployer {
function deploy(bytes memory _initCode, bytes32 _salt) external returns (address payable createdContract);
}

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface ERC20Basic {
function _totalSupply() external returns (uint256);
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function transfer(address to, uint256 value) external;
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IUSDT is ERC20Basic {
function allowance(address owner, address spender) external view returns (uint256);
function transferFrom(
address from,
address to,
uint256 value
) external;
function approve(address spender, uint256 value) external;
event Approval(address indexed owner, address indexed spender, uint256 value);
}

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../MerkleTreeWithHistory.sol";
contract MerkleTreeWithHistoryMock is MerkleTreeWithHistory {
constructor(uint32 _treeLevels, IHasher _hasher) MerkleTreeWithHistory(_treeLevels, _hasher) {}
function insert(bytes32 _leaf) public {
_insert(_leaf);
}
}

View File

@ -0,0 +1,9 @@
# Tornado Cash Classic
Tornado Cash Classic contracts forked from https://github.com/tornadocash/tornado-core/commit/54a7bdcb04aa9d29b3726cf1f177aa5e955ec626 version which is used for sidechains like BSC or OP
Some modifications
1. Using own IERC20.sol, SafeERC20.sol, and ReentrancyGuard.sol which is forked from the openzeppelin 3.4.1 version
2. Upgraded solidity version to the latest ^0.8.20

View File

@ -0,0 +1,123 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IHasher, MerkleTreeWithHistory } from "./MerkleTreeWithHistory.sol";
import { ReentrancyGuard } from "./libraries/ReentrancyGuard.sol";
interface IVerifier {
function verifyProof(bytes memory _proof, uint256[6] memory _input) external returns (bool);
}
abstract contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
IVerifier public immutable verifier;
uint256 public immutable denomination;
mapping(bytes32 => bool) public nullifierHashes;
// we store all commitments just to prevent accidental deposits with the same commitment
mapping(bytes32 => bool) public commitments;
event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp);
event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee);
/**
@dev The constructor
@param _verifier the address of SNARK verifier for this contract
@param _hasher the address of MiMC hash contract
@param _denomination transfer amount for each deposit
@param _merkleTreeHeight the height of deposits' Merkle Tree
*/
constructor(
IVerifier _verifier,
IHasher _hasher,
uint256 _denomination,
uint32 _merkleTreeHeight
) MerkleTreeWithHistory(_merkleTreeHeight, _hasher) {
require(_denomination > 0, "denomination should be greater than 0");
verifier = _verifier;
denomination = _denomination;
}
/**
@dev Deposit funds into the contract. The caller must send (for ETH) or approve (for ERC20) value equal to or `denomination` of this instance.
@param _commitment the note commitment, which is PedersenHash(nullifier + secret)
*/
function deposit(bytes32 _commitment) external payable nonReentrant {
require(!commitments[_commitment], "The commitment has been submitted");
uint32 insertedIndex = _insert(_commitment);
commitments[_commitment] = true;
_processDeposit();
emit Deposit(_commitment, insertedIndex, block.timestamp);
}
/** @dev this function is defined in a child contract */
function _processDeposit() internal virtual;
/**
@dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
`input` array consists of:
- merkle root of all deposits in the contract
- hash of unique deposit nullifier to prevent double spends
- the recipient of funds
- optional fee that goes to the transaction sender (usually a relay)
*/
function withdraw(
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) external payable nonReentrant {
require(_fee <= denomination, "Fee exceeds transfer value");
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
require(
verifier.verifyProof(
_proof,
[uint256(_root), uint256(_nullifierHash), uint256(uint160(address(_recipient))), uint256(uint160(address(_relayer))), _fee, _refund]
),
"Invalid withdraw proof"
);
nullifierHashes[_nullifierHash] = true;
_processWithdraw(_recipient, _relayer, _fee, _refund);
emit Withdrawal(_recipient, _nullifierHash, _relayer, _fee);
}
/** @dev this function is defined in a child contract */
function _processWithdraw(
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) internal virtual;
/** @dev whether a note is already spent */
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
return nullifierHashes[_nullifierHash];
}
/** @dev whether an array of notes is already spent */
function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) {
spent = new bool[](_nullifierHashes.length);
for (uint256 i = 0; i < _nullifierHashes.length; i++) {
if (isSpent(_nullifierHashes[i])) {
spent[i] = true;
}
}
}
}

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
interface ITornadoInstance {
function token() external view returns (address);
function denomination() external view returns (uint256);
function deposit(bytes32 commitment) external payable;
function withdraw(
bytes calldata proof,
bytes32 root,
bytes32 nullifierHash,
address payable recipient,
address payable relayer,
uint256 fee,
uint256 refund
) external payable;
}
contract TornadoProxyLight {
event EncryptedNote(address indexed sender, bytes encryptedNote);
function deposit(
ITornadoInstance _tornado,
bytes32 _commitment,
bytes calldata _encryptedNote
) external payable {
_tornado.deposit{ value: msg.value }(_commitment);
emit EncryptedNote(msg.sender, _encryptedNote);
}
function withdraw(
ITornadoInstance _tornado,
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) external payable {
_tornado.withdraw{ value: msg.value }(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund);
}
function backupNotes(bytes[] calldata _encryptedNotes) external {
for (uint256 i = 0; i < _encryptedNotes.length; i++) {
emit EncryptedNote(msg.sender, _encryptedNotes[i]);
}
}
}

View File

@ -0,0 +1,227 @@
// https://tornado.cash Verifier.sol generated by trusted setup ceremony.
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// 2019 OKIMS
pragma solidity ^0.8.20;
library Pairing {
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[0] * z + X[1]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/*
* @return The negation of p, i.e. p.plus(p.negate()) should be zero.
*/
function negate(G1Point memory p) internal pure returns (G1Point memory) {
// The prime q in the base field F_q for G1
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
} else {
return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q));
}
}
/*
* @return r the sum of two points of G1
*/
function plus(
G1Point memory p1,
G1Point memory p2
) internal view returns (G1Point memory r) {
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-add-failed");
}
/*
* @return r the product of a point on G1 and a scalar, i.e.
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
* points p.
*/
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-mul-failed");
}
/* @return The result of computing the pairing check
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
* For example,
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
*/
function pairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
G1Point memory c1,
G2Point memory c2,
G1Point memory d1,
G2Point memory d2
) internal view returns (bool) {
G1Point[4] memory p1 = [a1, b1, c1, d1];
G2Point[4] memory p2 = [a2, b2, c2, d2];
uint256 inputSize = 24;
uint256[] memory input = new uint256[](inputSize);
for (uint256 i = 0; i < 4; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-opcode-failed");
return out[0] != 0;
}
}
contract Verifier {
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
using Pairing for *;
struct VerifyingKey {
Pairing.G1Point alfa1;
Pairing.G2Point beta2;
Pairing.G2Point gamma2;
Pairing.G2Point delta2;
Pairing.G1Point[7] IC;
}
struct Proof {
Pairing.G1Point A;
Pairing.G2Point B;
Pairing.G1Point C;
}
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
vk.alfa1 = Pairing.G1Point(uint256(20692898189092739278193869274495556617788530808486270118371701516666252877969), uint256(11713062878292653967971378194351968039596396853904572879488166084231740557279));
vk.beta2 = Pairing.G2Point([uint256(12168528810181263706895252315640534818222943348193302139358377162645029937006), uint256(281120578337195720357474965979947690431622127986816839208576358024608803542)], [uint256(16129176515713072042442734839012966563817890688785805090011011570989315559913), uint256(9011703453772030375124466642203641636825223906145908770308724549646909480510)]);
vk.gamma2 = Pairing.G2Point([uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781)], [uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930)]);
vk.delta2 = Pairing.G2Point([uint256(21280594949518992153305586783242820682644996932183186320680800072133486887432), uint256(150879136433974552800030963899771162647715069685890547489132178314736470662)], [uint256(1081836006956609894549771334721413187913047383331561601606260283167615953295), uint256(11434086686358152335540554643130007307617078324975981257823476472104616196090)]);
vk.IC[0] = Pairing.G1Point(uint256(16225148364316337376768119297456868908427925829817748684139175309620217098814), uint256(5167268689450204162046084442581051565997733233062478317813755636162413164690));
vk.IC[1] = Pairing.G1Point(uint256(12882377842072682264979317445365303375159828272423495088911985689463022094260), uint256(19488215856665173565526758360510125932214252767275816329232454875804474844786));
vk.IC[2] = Pairing.G1Point(uint256(13083492661683431044045992285476184182144099829507350352128615182516530014777), uint256(602051281796153692392523702676782023472744522032670801091617246498551238913));
vk.IC[3] = Pairing.G1Point(uint256(9732465972180335629969421513785602934706096902316483580882842789662669212890), uint256(2776526698606888434074200384264824461688198384989521091253289776235602495678));
vk.IC[4] = Pairing.G1Point(uint256(8586364274534577154894611080234048648883781955345622578531233113180532234842), uint256(21276134929883121123323359450658320820075698490666870487450985603988214349407));
vk.IC[5] = Pairing.G1Point(uint256(4910628533171597675018724709631788948355422829499855033965018665300386637884), uint256(20532468890024084510431799098097081600480376127870299142189696620752500664302));
vk.IC[6] = Pairing.G1Point(uint256(15335858102289947642505450692012116222827233918185150176888641903531542034017), uint256(5311597067667671581646709998171703828965875677637292315055030353779531404812));
}
/*
* @returns Whether the proof is valid given the hardcoded verifying key
* above and the public inputs
*/
function verifyProof(
bytes memory proof,
uint256[6] memory input
) public view returns (bool) {
uint256[8] memory p = abi.decode(proof, (uint256[8]));
// Make sure that each element in the proof is less than the prime q
for (uint8 i = 0; i < p.length; i++) {
require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q");
}
Proof memory _proof;
_proof.A = Pairing.G1Point(p[0], p[1]);
_proof.B = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]);
_proof.C = Pairing.G1Point(p[6], p[7]);
VerifyingKey memory vk = verifyingKey();
// Compute the linear combination vk_x
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
vk_x = Pairing.plus(vk_x, vk.IC[0]);
// Make sure that every input is less than the snark scalar field
for (uint256 i = 0; i < input.length; i++) {
require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field");
vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
}
return Pairing.pairing(
Pairing.negate(_proof.A),
_proof.B,
vk.alfa1,
vk.beta2,
vk_x,
vk.gamma2,
_proof.C,
vk.delta2
);
}
}

View File

@ -0,0 +1,39 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IVerifier, IHasher, ERC20Tornado } from "./ERC20Tornado.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
contract cTornado is ERC20Tornado {
address public immutable governance = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
IERC20 public immutable comp;
constructor(
IERC20 _comp,
IVerifier _verifier,
IHasher _hasher,
uint256 _denomination,
uint32 _merkleTreeHeight,
IERC20 _token
) ERC20Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight, _token) {
require(address(_comp) != address(0), "Invalid COMP token address");
comp = _comp;
}
/// @dev Moves earned yield of the COMP token to the tornado governance contract
/// To make it work you might need to call `comptroller.claimComp(cPoolAddress)` first
function claimComp() external {
comp.transfer(governance, comp.balanceOf(address(this)));
}
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}

View File

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}

View File

@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IERC20 } from "../interfaces/IERC20.sol";
library SafeERC20 {
function safeTransfer(IERC20 token, address to, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, to, value));
// Return native revert data from token
if (!success) {
assembly {
revert(add(32, data), mload(data))
}
}
require(data.length == 0 || abi.decode(data, (bool)), 'SafeERC20: safeTransfer failed');
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
// Return native revert data from token
if (!success) {
assembly {
revert(add(32, data), mload(data))
}
}
require(data.length == 0 || abi.decode(data, (bool)), 'SafeERC20: safeTransferFrom failed');
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.approve.selector, spender, value));
// Return native revert data from token
if (!success) {
assembly {
revert(add(32, data), mload(data))
}
}
require(data.length == 0 || abi.decode(data, (bool)), 'SafeERC20: safeApprove failed');
}
}

View File

@ -0,0 +1,35 @@
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface IDeployer {
function deploy(bytes memory _initCode, bytes32 _salt) external returns (address payable createdContract);
}
contract Deployer {
IDeployer public immutable deployer;
constructor(IDeployer _deployer) public {
// Use EIP-2470 SingletonFactory address by default
deployer = address(_deployer) == address(0) ? IDeployer(0xce0042B868300000d44A59004Da54A005ffdcf9f) : _deployer;
emit Deployed(tx.origin, address(this));
}
event Deployed(address indexed sender, address indexed addr);
function deploy(bytes memory _initCode, bytes32 _salt) external {
address createdContract = deployer.deploy(_initCode, _salt);
require(createdContract != address(0), "Deploy failed");
emit Deployed(msg.sender, createdContract);
}
}

View File

@ -0,0 +1,164 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
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";
/// @dev contract which calculates the fee for each pool
contract FeeManager {
using SafeMath for uint256;
uint256 public constant PROTOCOL_FEE_DIVIDER = 10000;
address public immutable torn;
address public immutable governance;
InstanceRegistry public immutable registry;
uint24 public uniswapTornPoolSwappingFee;
uint32 public uniswapTimePeriod;
uint24 public updateFeeTimeLimit;
mapping(ITornadoInstance => uint160) public instanceFee;
mapping(ITornadoInstance => uint256) public instanceFeeUpdated;
event FeeUpdated(address indexed instance, uint256 newFee);
event UniswapTornPoolSwappingFeeChanged(uint24 newFee);
modifier onlyGovernance() {
require(msg.sender == governance);
_;
}
struct Deviation {
address instance;
int256 deviation; // in 10**-1 percents, so it can be like -2.3% if the price of TORN declined
}
constructor(
address _torn,
address _governance,
address _registry
) public {
torn = _torn;
governance = _governance;
registry = InstanceRegistry(_registry);
}
/**
* @notice This function should update the fees of each pool
*/
function updateAllFees() external {
updateFees(registry.getAllInstanceAddresses());
}
/**
* @notice This function should update the fees for tornado instances
* (here called pools)
* @param _instances pool addresses to update fees for
* */
function updateFees(ITornadoInstance[] memory _instances) public {
for (uint256 i = 0; i < _instances.length; i++) {
updateFee(_instances[i]);
}
}
/**
* @notice This function should update the fee of a specific pool
* @param _instance address of the pool to update fees for
*/
function updateFee(ITornadoInstance _instance) public {
uint160 newFee = calculatePoolFee(_instance);
instanceFee[_instance] = newFee;
instanceFeeUpdated[_instance] = now;
emit FeeUpdated(address(_instance), newFee);
}
/**
* @notice This function should return the fee of a specific pool and update it if the time has come
* @param _instance address of the pool to get fees for
*/
function instanceFeeWithUpdate(ITornadoInstance _instance) public returns (uint160) {
if (now - instanceFeeUpdated[_instance] > updateFeeTimeLimit) {
updateFee(_instance);
}
return instanceFee[_instance];
}
/**
* @notice function to update a single fee entry
* @param _instance instance for which to update data
* @return newFee the new fee pool
*/
function calculatePoolFee(ITornadoInstance _instance) public virtual view returns (uint160) {
(bool isERC20, IERC20 token, , uint24 uniswapPoolSwappingFee, uint32 protocolFeePercentage) = registry.instances(_instance);
if (protocolFeePercentage == 0) {
return 0;
}
token = token == IERC20(0) && !isERC20 ? IERC20(UniswapV3OracleHelper.WETH) : token; // for eth instances
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
[torn, address(token)],
[uniswapTornPoolSwappingFee, uniswapPoolSwappingFee],
uniswapTimePeriod
);
// prettier-ignore
return
uint160(
_instance
.denomination()
.mul(UniswapV3OracleHelper.RATIO_DIVIDER)
.div(tokenPriceRatio)
.mul(uint256(protocolFeePercentage))
.div(PROTOCOL_FEE_DIVIDER)
);
}
/**
* @notice function to update the uniswap fee
* @param _uniswapTornPoolSwappingFee new uniswap fee
*/
function setUniswapTornPoolSwappingFee(uint24 _uniswapTornPoolSwappingFee) public onlyGovernance {
uniswapTornPoolSwappingFee = _uniswapTornPoolSwappingFee;
emit UniswapTornPoolSwappingFeeChanged(uniswapTornPoolSwappingFee);
}
/**
* @notice This function should allow governance to set a new period for twap measurement
* @param newPeriod the new period to use
* */
function setPeriodForTWAPOracle(uint32 newPeriod) external onlyGovernance {
uniswapTimePeriod = newPeriod;
}
/**
* @notice This function should allow governance to set a new update fee time limit for instance fee updating
* @param newLimit the new time limit to use
* */
function setUpdateFeeTimeLimit(uint24 newLimit) external onlyGovernance {
updateFeeTimeLimit = newLimit;
}
/**
* @notice returns fees deviations for each instance, so it can be easily seen what instance requires an update
*/
function feeDeviations() public view returns (Deviation[] memory results) {
ITornadoInstance[] memory instances = registry.getAllInstanceAddresses();
results = new Deviation[](instances.length);
for (uint256 i = 0; i < instances.length; i++) {
uint256 marketFee = calculatePoolFee(instances[i]);
int256 deviation;
if (marketFee != 0) {
deviation = int256((instanceFee[instances[i]] * 1000) / marketFee) - 1000;
}
results[i] = Deviation({ instance: address(instances[i]), deviation: deviation });
}
}
}

View File

@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Initializable } from "@openzeppelin/contracts-v3/proxy/Initializable.sol";
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;
}
contract InstanceRegistry is Initializable {
using SafeERC20 for IERC20;
enum InstanceState {
DISABLED,
ENABLED
}
struct Instance {
bool isERC20;
IERC20 token;
InstanceState state;
// the fee of the uniswap pool which will be used to get a TWAP
uint24 uniswapPoolSwappingFee;
// the fee the protocol takes from relayer, it should be multiplied by PROTOCOL_FEE_DIVIDER from FeeManager.sol
uint32 protocolFeePercentage;
}
struct Tornado {
ITornadoInstance addr;
Instance instance;
}
address public immutable governance;
ITornadoRouter public router;
mapping(ITornadoInstance => Instance) public instances;
ITornadoInstance[] public instanceIds;
event InstanceStateUpdated(ITornadoInstance indexed instance, InstanceState state);
event RouterRegistered(address tornadoRouter);
modifier onlyGovernance() {
require(msg.sender == governance, "Not authorized");
_;
}
constructor(address _governance) public {
governance = _governance;
}
function initialize(Tornado[] memory _instances, address _router) external initializer {
router = ITornadoRouter(_router);
for (uint256 i = 0; i < _instances.length; i++) {
_updateInstance(_instances[i]);
instanceIds.push(_instances[i].addr);
}
}
/**
* @dev Add or update an instance.
*/
function updateInstance(Tornado calldata _tornado) external virtual onlyGovernance {
require(_tornado.instance.state != InstanceState.DISABLED, "Use removeInstance() for remove");
if (instances[_tornado.addr].state == InstanceState.DISABLED) {
instanceIds.push(_tornado.addr);
}
_updateInstance(_tornado);
}
/**
* @dev Remove an instance.
* @param _instanceId The instance id in `instanceIds` mapping to remove.
*/
function removeInstance(uint256 _instanceId) external virtual onlyGovernance {
ITornadoInstance _instance = instanceIds[_instanceId];
(bool isERC20, IERC20 token) = (instances[_instance].isERC20, instances[_instance].token);
if (isERC20) {
uint256 allowance = token.allowance(address(router), address(_instance));
if (allowance != 0) {
router.approveExactToken(token, address(_instance), 0);
}
}
delete instances[_instance];
instanceIds[_instanceId] = instanceIds[instanceIds.length - 1];
instanceIds.pop();
emit InstanceStateUpdated(_instance, InstanceState.DISABLED);
}
/**
* @notice This function should allow governance to set a new protocol fee for relayers
* @param instance the to update
* @param newFee the new fee to use
* */
function setProtocolFee(ITornadoInstance instance, uint32 newFee) external onlyGovernance {
instances[instance].protocolFeePercentage = newFee;
}
/**
* @notice This function should allow governance to set a new tornado proxy address
* @param routerAddress address of the new proxy
* */
function setTornadoRouter(address routerAddress) external onlyGovernance {
router = ITornadoRouter(routerAddress);
emit RouterRegistered(routerAddress);
}
function _updateInstance(Tornado memory _tornado) internal virtual {
instances[_tornado.addr] = _tornado.instance;
if (_tornado.instance.isERC20) {
IERC20 token = IERC20(_tornado.addr.token());
require(token == _tornado.instance.token, "Incorrect token");
uint256 allowance = token.allowance(address(router), address(_tornado.addr));
if (allowance == 0) {
router.approveExactToken(token, address(_tornado.addr), type(uint256).max);
}
}
emit InstanceStateUpdated(_tornado.addr, _tornado.instance.state);
}
/**
* @dev Returns all instance configs
*/
function getAllInstances() public view returns (Tornado[] memory result) {
result = new Tornado[](instanceIds.length);
for (uint256 i = 0; i < instanceIds.length; i++) {
ITornadoInstance _instance = instanceIds[i];
result[i] = Tornado({ addr: _instance, instance: instances[_instance] });
}
}
/**
* @dev Returns all instance addresses
*/
function getAllInstanceAddresses() public view returns (ITornadoInstance[] memory result) {
result = new ITornadoInstance[](instanceIds.length);
for (uint256 i = 0; i < instanceIds.length; i++) {
result[i] = instanceIds[i];
}
}
/// @notice get erc20 tornado instance token
/// @param instance the interface (contract) key to the instance data
function getPoolToken(ITornadoInstance instance) external view returns (address) {
return address(instances[instance].token);
}
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts-v3/proxy/TransparentUpgradeableProxy.sol";
/**
* @dev TransparentUpgradeableProxy that sets its admin to the implementation itself.
* It is also allowed to call implementation methods.
*/
contract LoopbackProxy is TransparentUpgradeableProxy {
/**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/
constructor(address _logic, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, address(this), _data)
{ }
/**
* @dev Override to allow admin (itself) access the fallback function.
*/
function _beforeFallback() internal override { }
}

View File

@ -0,0 +1,7 @@
# Tornado Cash Governance
Latest version of Tornado Cash Governance contracts with following changes
1. Make import work on this repository
2. Contracts no longer require ENS.resolve() function to find each other (This is no longer necessary and the mainnet contract.tornadocash.eth still under hands of an unknown EOA address).

View File

@ -0,0 +1,364 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v3/proxy/Initializable.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { TORN } from "./TORN/TORN.sol";
import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
import { ITornadoInstance } from "./interfaces/ITornadoInstance.sol";
interface IENS {
function owner(bytes32 node) external view returns (address);
}
/*
* @dev Solidity implementation of the ENS namehash algorithm.
*
* Warning! Does not normalize or validate names before hashing.
* Original version can be found here https://github.com/JonahGroendal/ens-namehash/
*/
library ENSNamehash {
function namehash(bytes memory domain) internal pure returns (bytes32) {
return namehash(domain, 0);
}
function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 len = labelLength(domain, i);
return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
}
function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
uint256 len;
while (i + len != domain.length && domain[i + len] != 0x2e) {
len++;
}
return len;
}
function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) {
require(offset + len <= data.length);
assembly {
ret := keccak256(add(add(data, 32), offset), len)
}
}
}
interface IFeeManager {
function instanceFeeWithUpdate(ITornadoInstance _instance) external returns (uint160);
}
struct RelayerState {
uint256 balance;
bytes32 ensHash;
}
/**
* @notice Registry contract, one of the main contracts of this protocol upgrade.
* The contract should store relayers' addresses and data attributed to the
* master address of the relayer. This data includes the relayers stake and
* his ensHash.
* A relayers master address has a number of subaddresses called "workers",
* these are all addresses which burn stake in communication with the proxy.
* If a relayer is not registered, he is not displayed on the frontend.
* @dev CONTRACT RISKS:
* - if setter functions are compromised, relayer metadata would be at risk, including the noted amount of his balance
* - if burn function is compromised, relayers run the risk of being unable to handle withdrawals
* - the above risk also applies to the nullify balance function
*
*/
contract RelayerRegistry is Initializable {
using SafeMath for uint256;
using SafeERC20 for TORN;
using ENSNamehash for bytes;
TORN public immutable torn;
address public immutable governance;
IENS public immutable ens;
TornadoStakingRewards public immutable staking;
IFeeManager public immutable feeManager;
address public tornadoRouter;
uint256 public minStakeAmount;
mapping(address => RelayerState) public relayers;
mapping(address => address) public workers;
event RelayerBalanceNullified(address relayer);
event WorkerRegistered(address relayer, address worker);
event WorkerUnregistered(address relayer, address worker);
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
event StakeBurned(address relayer, uint256 amountBurned);
event MinimumStakeAmount(uint256 minStakeAmount);
event RouterRegistered(address tornadoRouter);
event RelayerRegistered(bytes32 relayer, string ensName, address relayerAddress, uint256 stakedAmount);
modifier onlyGovernance() {
require(msg.sender == governance, "only governance");
_;
}
modifier onlyTornadoRouter() {
require(msg.sender == tornadoRouter, "only proxy");
_;
}
modifier onlyRelayer(address sender, address relayer) {
require(workers[sender] == relayer, "only relayer");
_;
}
constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager)
public
{
torn = TORN(_torn);
governance = _governance;
ens = IENS(_ens);
staking = TornadoStakingRewards(_staking);
feeManager = IFeeManager(_feeManager);
}
/**
* @notice initialize function for upgradeability
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
* params left out because self explainable
*
*/
function initialize(address _tornadoRouter) external initializer {
tornadoRouter = _tornadoRouter;
}
/**
* @notice This function should register a master address and optionally a set of workeres for a relayer + metadata
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender check) can always unregister itself
* @param ensName ens name of the relayer
* @param stake the initial amount of stake in TORN the relayer is depositing
*
*/
function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister)
external
{
_register(msg.sender, ensName, stake, workersToRegister);
}
/**
* @dev Register function equivalent with permit-approval instead of regular approve.
*
*/
function registerPermit(
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister,
address relayer,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(relayer, address(this), stake, deadline, v, r, s);
_register(relayer, ensName, stake, workersToRegister);
}
function _register(
address relayer,
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister
) internal {
bytes32 ensHash = bytes(ensName).namehash();
require(relayer == ens.owner(ensHash), "only ens owner");
require(workers[relayer] == address(0), "cant register again");
RelayerState storage metadata = relayers[relayer];
require(metadata.ensHash == bytes32(0), "registered already");
require(stake >= minStakeAmount, "!min_stake");
torn.safeTransferFrom(relayer, address(staking), stake);
emit StakeAddedToRelayer(relayer, stake);
metadata.balance = stake;
metadata.ensHash = ensHash;
workers[relayer] = relayer;
for (uint256 i = 0; i < workersToRegister.length; i++) {
address worker = workersToRegister[i];
_registerWorker(relayer, worker);
}
emit RelayerRegistered(ensHash, ensName, relayer, stake);
}
/**
* @notice This function should allow relayers to register more workeres
* @param relayer Relayer which should send message from any worker which is already registered
* @param worker Address to register
*
*/
function registerWorker(address relayer, address worker) external onlyRelayer(msg.sender, relayer) {
_registerWorker(relayer, worker);
}
function _registerWorker(address relayer, address worker) internal {
require(workers[worker] == address(0), "can't steal an address");
workers[worker] = relayer;
emit WorkerRegistered(relayer, worker);
}
/**
* @notice This function should allow anybody to unregister an address they own
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
* - this should be followed by an action like burning relayer stake
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour, this feature was not included in the end
* - reverts if trying to unregister master, otherwise contract would break. in general, there should be no reason to unregister master at all
*
*/
function unregisterWorker(address worker) external {
if (worker != msg.sender) require(workers[worker] == msg.sender, "only owner of worker");
require(workers[worker] != worker, "cant unregister master");
emit WorkerUnregistered(workers[worker], worker);
workers[worker] = address(0);
}
/**
* @notice This function should allow anybody to stake to a relayer more TORN
* @param relayer Relayer main address to stake to
* @param stake Stake to be added to relayer
*
*/
function stakeToRelayer(address relayer, uint256 stake) external {
_stakeToRelayer(msg.sender, relayer, stake);
}
/**
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
* @param staker address from that stake is paid
*
*/
function stakeToRelayerPermit(
address relayer,
uint256 stake,
address staker,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(staker, address(this), stake, deadline, v, r, s);
_stakeToRelayer(staker, relayer, stake);
}
function _stakeToRelayer(address staker, address relayer, uint256 stake) internal {
require(workers[relayer] == relayer, "!registered");
torn.safeTransferFrom(staker, address(staking), stake);
relayers[relayer].balance = stake.add(relayers[relayer].balance);
emit StakeAddedToRelayer(relayer, stake);
}
/**
* @notice This function should burn some relayer stake on withdraw and notify staking of this
* @dev IMPORTANT FUNCTION:
* - This should be only called by the tornado proxy
* - Should revert if relayer does not call proxy from valid worker
* - Should not overflow
* - Should underflow and revert (SafeMath) on not enough stake (balance)
* @param sender worker to check sender == relayer
* @param relayer address of relayer who's stake is being burned
* @param pool instance to get fee for
*
*/
function burn(address sender, address relayer, ITornadoInstance pool) external onlyTornadoRouter {
address masterAddress = workers[sender];
if (masterAddress == address(0)) {
require(workers[relayer] == address(0), "Only custom relayer");
return;
}
require(masterAddress == relayer, "only relayer");
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
staking.addBurnRewards(toBurn);
emit StakeBurned(relayer, toBurn);
}
/**
* @notice This function should allow governance to set the minimum stake amount
* @param minAmount new minimum stake amount
*
*/
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
minStakeAmount = minAmount;
emit MinimumStakeAmount(minAmount);
}
/**
* @notice This function should allow governance to set a new tornado proxy address
* @param tornadoRouterAddress address of the new proxy
*
*/
function setTornadoRouter(address tornadoRouterAddress) external onlyGovernance {
tornadoRouter = tornadoRouterAddress;
emit RouterRegistered(tornadoRouterAddress);
}
/**
* @notice This function should allow governance to nullify a relayers balance
* @dev IMPORTANT FUNCTION:
* - Should nullify the balance
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds (for gov to operate with them)
* @param relayer address of relayer who's balance is to nullify
*
*/
function nullifyBalance(address relayer) external onlyGovernance {
address masterAddress = workers[relayer];
require(relayer == masterAddress, "must be master");
relayers[masterAddress].balance = 0;
emit RelayerBalanceNullified(relayer);
}
/**
* @notice This function should check if a worker is associated with a relayer
* @param toResolve address to check
* @return true if is associated
*
*/
function isRelayer(address toResolve) external view returns (bool) {
return workers[toResolve] != address(0);
}
/**
* @notice This function should check if a worker is registered to the relayer stated
* @param relayer relayer to check
* @param toResolve address to check
* @return true if registered
*
*/
function isRelayerRegistered(address relayer, address toResolve) external view returns (bool) {
return workers[toResolve] == relayer;
}
/**
* @notice This function should get a relayers ensHash
* @param relayer address to fetch for
* @return relayer's ensHash
*
*/
function getRelayerEnsHash(address relayer) external view returns (bytes32) {
return relayers[workers[relayer]].ensHash;
}
/**
* @notice This function should get a relayers balance
* @param relayer relayer who's balance is to fetch
* @return relayer's balance
*
*/
function getRelayerBalance(address relayer) external view returns (uint256) {
return relayers[workers[relayer]].balance;
}
}

View File

@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { EnsResolve } from "./ENS.sol";
contract Airdrop is EnsResolve {
struct Recipient {
address to;
uint256 amount;
}
constructor(bytes32 tokenAddress, Recipient[] memory targets) public {
IERC20 token = IERC20(resolve(tokenAddress));
require(token.balanceOf(address(this)) > 0, "Balance is 0, airdrop already done");
for (uint256 i = 0; i < targets.length; i++) {
token.transfer(targets[i].to, targets[i].amount);
}
selfdestruct(address(0));
}
}

View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
// A copy from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/files
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := mload(add(signature, 0x41))
}
return recover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface ENS {
function resolver(bytes32 node) external view returns (Resolver);
}
interface Resolver {
function addr(bytes32 node) external view returns (address);
}
contract EnsResolve {
function resolve(bytes32 node) public view virtual returns (address) {
ENS Registry = ENS(
getChainId() == 1 ? 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e : 0x8595bFb0D940DfEDC98943FA8a907091203f25EE
);
return Registry.resolver(node).addr(node);
}
function bulkResolve(bytes32[] memory domains) public view returns (address[] memory result) {
result = new address[](domains.length);
for (uint256 i = 0; i < domains.length; i++) {
result[i] = resolve(domains[i]);
}
}
function getChainId() internal pure returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
}

View File

@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
// Adapted copy from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/files
import { ERC20 } from "@openzeppelin/contracts-v3/token/ERC20/ERC20.sol";
import { ECDSA } from "./ECDSA.sol";
/**
* @dev Extension of {ERC20} that allows token holders to use their tokens
* without sending any transactions by setting {IERC20-allowance} with a
* signature using the {permit} method, and then spend them via
* {IERC20-transferFrom}.
*
* The {permit} signature mechanism conforms to the {IERC2612Permit} interface.
*/
abstract contract ERC20Permit is ERC20 {
mapping(address => uint256) private _nonces;
bytes32 private constant _PERMIT_TYPEHASH = keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
// Mapping of ChainID to domain separators. This is a very gas efficient way
// to not recalculate the domain separator on every call, while still
// automatically detecting ChainID changes.
mapping(uint256 => bytes32) private _domainSeparators;
constructor() internal {
_updateDomainSeparator();
}
/**
* @dev See {IERC2612Permit-permit}.
*
* If https://eips.ethereum.org/EIPS/eip-1344[ChainID] ever changes, the
* EIP712 Domain Separator is automatically recalculated.
*/
function permit(
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public {
require(blockTimestamp() <= deadline, "ERC20Permit: expired deadline");
bytes32 hashStruct = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner], deadline));
bytes32 hash = keccak256(abi.encodePacked(uint16(0x1901), _domainSeparator(), hashStruct));
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_nonces[owner]++;
_approve(owner, spender, amount);
}
/**
* @dev See {IERC2612Permit-nonces}.
*/
function nonces(address owner) public view returns (uint256) {
return _nonces[owner];
}
function _updateDomainSeparator() private returns (bytes32) {
uint256 _chainID = chainID();
bytes32 newDomainSeparator = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name())),
keccak256(bytes("1")), // Version
_chainID,
address(this)
)
);
_domainSeparators[_chainID] = newDomainSeparator;
return newDomainSeparator;
}
// Returns the domain separator, updating it if chainID changes
function _domainSeparator() private returns (bytes32) {
bytes32 domainSeparator = _domainSeparators[chainID()];
if (domainSeparator != 0x00) {
return domainSeparator;
} else {
return _updateDomainSeparator();
}
}
function chainID() public view virtual returns (uint256 _chainID) {
assembly {
_chainID := chainid()
}
}
function blockTimestamp() public view virtual returns (uint256) {
return block.timestamp;
}
}

View File

@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { ERC20 } from "@openzeppelin/contracts-v3/token/ERC20/ERC20.sol";
import { ERC20Burnable } from "@openzeppelin/contracts-v3/token/ERC20/ERC20Burnable.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { Ownable } from "@openzeppelin/contracts-v3/access/Ownable.sol";
import { Pausable } from "@openzeppelin/contracts-v3/utils/Pausable.sol";
import { Math } from "@openzeppelin/contracts-v3/math/Math.sol";
import { ERC20Permit } from "./ERC20Permit.sol";
contract TORN is ERC20("TornadoCash", "TORN"), ERC20Burnable, ERC20Permit, Pausable {
using SafeERC20 for IERC20;
uint256 public immutable canUnpauseAfter;
address public immutable governance;
mapping(address => bool) public allowedTransferee;
event Allowed(address target);
event Disallowed(address target);
struct Recipient {
address to;
uint256 amount;
}
constructor(
address _governance,
uint256 _pausePeriod,
Recipient[] memory _vestings
) public {
address _resolvedGovernance = _governance;
governance = _resolvedGovernance;
allowedTransferee[_resolvedGovernance] = true;
for (uint256 i = 0; i < _vestings.length; i++) {
address to = _vestings[i].to;
_mint(to, _vestings[i].amount);
allowedTransferee[to] = true;
}
canUnpauseAfter = blockTimestamp().add(_pausePeriod);
_pause();
require(totalSupply() == 10000000 ether, "TORN: incorrect distribution");
}
modifier onlyGovernance() {
require(_msgSender() == governance, "TORN: only governance can perform this action");
_;
}
function changeTransferability(bool decision) public onlyGovernance {
require(blockTimestamp() > canUnpauseAfter, "TORN: cannot change transferability yet");
if (decision) {
_unpause();
} else {
_pause();
}
}
function addToAllowedList(address[] memory target) public onlyGovernance {
for (uint256 i = 0; i < target.length; i++) {
allowedTransferee[target[i]] = true;
emit Allowed(target[i]);
}
}
function removeFromAllowedList(address[] memory target) public onlyGovernance {
for (uint256 i = 0; i < target.length; i++) {
allowedTransferee[target[i]] = false;
emit Disallowed(target[i]);
}
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override {
super._beforeTokenTransfer(from, to, amount);
require(!paused() || allowedTransferee[from] || allowedTransferee[to], "TORN: paused");
require(to != address(this), "TORN: invalid recipient");
}
/// @dev Method to claim junk and accidentally sent tokens
function rescueTokens(
IERC20 _token,
address payable _to,
uint256 _balance
) external onlyGovernance {
require(_to != address(0), "TORN: can not send to zero address");
if (_token == IERC20(0)) {
// for Ether
uint256 totalBalance = address(this).balance;
uint256 balance = _balance == 0 ? totalBalance : Math.min(totalBalance, _balance);
_to.transfer(balance);
} else {
// any other erc20
uint256 totalBalance = _token.balanceOf(address(this));
uint256 balance = _balance == 0 ? totalBalance : Math.min(totalBalance, _balance);
require(balance > 0, "TORN: trying to send 0 balance");
_token.safeTransfer(_to, balance);
}
}
}

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts-v3/math/Math.sol";
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
/**
* @title Vesting
* @dev A token holder contract that can release its token balance gradually like a
* typical vesting scheme, with a cliff and vesting period. Optionally revocable by the
* owner.
*/
contract Vesting {
using SafeERC20 for IERC20;
using SafeMath for uint256;
uint256 public constant SECONDS_PER_MONTH = 30 days;
event Released(uint256 amount);
// beneficiary of tokens after they are released
address public immutable beneficiary;
IERC20 public immutable token;
uint256 public immutable cliffInMonths;
uint256 public immutable startTimestamp;
uint256 public immutable durationInMonths;
uint256 public released;
/**
* @dev Creates a vesting contract that vests its balance of any ERC20 token to the
* _beneficiary, monthly in a linear fashion until duration has passed. By then all
* of the balance will have vested.
* @param _beneficiary address of the beneficiary to whom vested tokens are transferred
* @param _cliffInMonths duration in months of the cliff in which tokens will begin to vest
* @param _durationInMonths duration in months of the period in which the tokens will vest
*/
constructor(
address _token,
address _beneficiary,
uint256 _startTimestamp,
uint256 _cliffInMonths,
uint256 _durationInMonths
) public {
require(_beneficiary != address(0), "Beneficiary cannot be empty");
require(_cliffInMonths <= _durationInMonths, "Cliff is greater than duration");
token = IERC20(_token);
beneficiary = _beneficiary;
durationInMonths = _durationInMonths;
cliffInMonths = _cliffInMonths;
startTimestamp = _startTimestamp == 0 ? blockTimestamp() : _startTimestamp;
}
/**
* @notice Transfers vested tokens to beneficiary.
*/
function release() external {
uint256 vested = vestedAmount();
require(vested > 0, "No tokens to release");
released = released.add(vested);
token.safeTransfer(beneficiary, vested);
emit Released(vested);
}
/**
* @dev Calculates the amount that has already vested but hasn't been released yet.
*/
function vestedAmount() public view returns (uint256) {
if (blockTimestamp() < startTimestamp) {
return 0;
}
uint256 elapsedTime = blockTimestamp().sub(startTimestamp);
uint256 elapsedMonths = elapsedTime.div(SECONDS_PER_MONTH);
if (elapsedMonths < cliffInMonths) {
return 0;
}
// If over vesting duration, all tokens vested
if (elapsedMonths >= durationInMonths) {
return token.balanceOf(address(this));
} else {
uint256 currentBalance = token.balanceOf(address(this));
uint256 totalBalance = currentBalance.add(released);
uint256 vested = totalBalance.mul(elapsedMonths).div(durationInMonths);
uint256 unreleased = vested.sub(released);
// currentBalance can be 0 in case of vesting being revoked earlier.
return Math.min(currentBalance, unreleased);
}
}
function blockTimestamp() public view virtual returns (uint256) {
return block.timestamp;
}
}

View File

@ -0,0 +1,66 @@
/**
* This is tornado.cash airdrop for early adopters. In order to claim your TORN token please follow https://tornado.cash/airdrop
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import { ERC20 } from "@openzeppelin/contracts-v3/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { EnsResolve } from "./ENS.sol";
contract Voucher is ERC20("TornadoCash voucher for early adopters", "vTORN"), EnsResolve {
using SafeERC20 for IERC20;
IERC20 public immutable torn;
uint256 public immutable expiresAt;
address public immutable governance;
mapping(address => bool) public allowedTransferee;
struct Recipient {
address to;
uint256 amount;
}
constructor(
bytes32 _torn,
bytes32 _governance,
uint256 _duration,
Recipient[] memory _airdrops
) public {
torn = IERC20(resolve(_torn));
governance = resolve(_governance);
expiresAt = blockTimestamp().add(_duration);
for (uint256 i = 0; i < _airdrops.length; i++) {
_mint(_airdrops[i].to, _airdrops[i].amount);
allowedTransferee[_airdrops[i].to] = true;
}
}
function redeem() external {
require(blockTimestamp() < expiresAt, "Airdrop redeem period has ended");
uint256 amount = balanceOf(msg.sender);
_burn(msg.sender, amount);
torn.safeTransfer(msg.sender, amount);
}
function rescueExpiredTokens() external {
require(blockTimestamp() >= expiresAt, "Airdrop redeem period has not ended yet");
torn.safeTransfer(governance, torn.balanceOf(address(this)));
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override {
super._beforeTokenTransfer(from, to, amount);
require(to == address(0) || from == address(0) || allowedTransferee[from], "ERC20: transfer is not allowed");
}
function blockTimestamp() public view virtual returns (uint256) {
return block.timestamp;
}
}

View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../Airdrop.sol";
contract AirdropMock is Airdrop {
constructor(bytes32 tokenAddress, Recipient[] memory targets) public Airdrop(tokenAddress, targets) {}
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
contract ENSMock {
mapping(bytes32 => address) public registry;
function resolver(
bytes32 /* _node */
) external view returns (address) {
return address(this);
}
function addr(bytes32 _node) external view returns (address) {
return registry[_node];
}
function setAddr(bytes32 _node, address _addr) external {
registry[_node] = _addr;
}
function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
require(success);
results[i] = result;
}
return results;
}
}

View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../TORN.sol";
import "./Timestamp.sol";
contract TORNMock is TORN, Timestamp {
uint256 public chainId;
constructor(
address _governance,
uint256 _pausePeriod,
Recipient[] memory _vesting
) public TORN(_governance, _pausePeriod, _vesting) {}
/**
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
**/
function setChainId(uint256 _chainId) public {
chainId = _chainId;
}
function chainID() public view override returns (uint256) {
return chainId;
}
function blockTimestamp() public view override(Timestamp, ERC20Permit) returns (uint256) {
return Timestamp.blockTimestamp();
}
}

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Timestamp {
uint256 public fakeTimestamp;
function setFakeTimestamp(uint256 _fakeTimestamp) public {
fakeTimestamp = _fakeTimestamp;
}
function blockTimestamp() public view virtual returns (uint256) {
return fakeTimestamp == 0 ? block.timestamp : fakeTimestamp;
}
}

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import "../Vesting.sol";
import "./Timestamp.sol";
contract VestingMock is Vesting, Timestamp {
constructor(
address _token,
address _beneficiary,
uint256 _startTimestamp,
uint256 _cliffInMonths,
uint256 _durationInMonths
) public Vesting(_token, _beneficiary, _startTimestamp, _cliffInMonths, _durationInMonths) {}
/**
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
**/
function blockTimestamp() public view override(Timestamp, Vesting) returns (uint256) {
return Timestamp.blockTimestamp();
}
}

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import "../Voucher.sol";
import "./Timestamp.sol";
contract VoucherMock is Voucher, Timestamp {
constructor(
bytes32 _torn,
bytes32 _governance,
uint256 _duration,
Recipient[] memory _airdrops
) public Voucher(_torn, _governance, _duration, _airdrops) {}
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
function blockTimestamp() public view override(Timestamp, Voucher) returns (uint256) {
return Timestamp.blockTimestamp();
}
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { FeeManager, IERC20, ITornadoInstance, UniswapV3OracleHelper } from "../FeeManager.sol";
import { OwnableLibrary } from "../libraries/OwnableLibrary.sol";
/**
* @dev Stores and returns overrided TORN-Token price
Literally you don't need to do this unless you don't want to occupy another storage slot for the contract
*/
library TokenPriceLibrary {
struct PriceStorage {
mapping(address => uint256) _prices;
}
bytes32 private constant PriceStorageLocation = keccak256(abi.encode(uint256(keccak256("tornado.storage.price"))));
function _getPriceStorage() private pure returns (PriceStorage storage pointer) {
bytes32 slot = PriceStorageLocation;
assembly {
pointer_slot := slot
}
}
function setPrice(address _token, uint256 _price) internal {
PriceStorage storage priceStorage = _getPriceStorage();
priceStorage._prices[_token] = _price;
}
function getPrice(address _token) internal view returns (uint256 price) {
PriceStorage storage priceStorage = _getPriceStorage();
price = priceStorage._prices[_token];
}
}
/**
* @dev A testnet feeManager that the maintainer could override V3 oracles, assuming it has been deployed with TestnetProxy
*/
contract TestnetFeeManager is FeeManager {
constructor(
address _torn,
address _governance,
address _registry
) public FeeManager(_torn, _governance, _registry) {}
modifier onlyOwner {
require(OwnableLibrary.getOwner() == msg.sender, "Not an owner");
_;
}
function setTokenPrice(address _token, uint256 _price) external onlyOwner {
TokenPriceLibrary.setPrice(_token, _price);
}
function getTokenPriceRatio(address _token, uint24 _uniswapPoolSwappingFee) public view returns (uint256) {
return TokenPriceLibrary.getPrice(_token) != 0
? TokenPriceLibrary.getPrice(_token)
: UniswapV3OracleHelper.getPriceRatioOfTokens(
[torn, _token],
[uniswapTornPoolSwappingFee, _uniswapPoolSwappingFee],
uniswapTimePeriod
);
}
function calculatePoolFee(ITornadoInstance _instance) public override view returns (uint160) {
(bool isERC20, IERC20 token, , uint24 uniswapPoolSwappingFee, uint32 protocolFeePercentage) = registry.instances(_instance);
if (protocolFeePercentage == 0) {
return 0;
}
token = token == IERC20(0) && !isERC20 ? IERC20(UniswapV3OracleHelper.WETH) : token; // for eth instances
uint256 tokenPriceRatio = getTokenPriceRatio(address(token), uniswapPoolSwappingFee);
return uint160(
_instance
.denomination()
.mul(UniswapV3OracleHelper.RATIO_DIVIDER)
.div(tokenPriceRatio)
.mul(uint256(protocolFeePercentage))
.div(PROTOCOL_FEE_DIVIDER)
);
}
}

View File

@ -0,0 +1,23 @@
// 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

@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts-v3/math/Math.sol";
import { ITornadoInstance } from "./interfaces/ITornadoInstance.sol";
import { InstanceRegistry } from "./InstanceRegistry.sol";
import { RelayerRegistry } from "./RelayerRegistry.sol";
contract TornadoRouter {
using SafeERC20 for IERC20;
event EncryptedNote(address indexed sender, bytes encryptedNote);
address public immutable governance;
InstanceRegistry public immutable instanceRegistry;
RelayerRegistry public immutable relayerRegistry;
modifier onlyGovernance() {
require(msg.sender == governance, "Not authorized");
_;
}
modifier onlyInstanceRegistry() {
require(msg.sender == address(instanceRegistry), "Not authorized");
_;
}
constructor(
address _governance,
address _instanceRegistry,
address _relayerRegistry
) public {
governance = _governance;
instanceRegistry = InstanceRegistry(_instanceRegistry);
relayerRegistry = RelayerRegistry(_relayerRegistry);
}
function deposit(
ITornadoInstance _tornado,
bytes32 _commitment,
bytes calldata _encryptedNote
) public payable virtual {
(bool isERC20, IERC20 token, InstanceRegistry.InstanceState state, , ) = instanceRegistry.instances(_tornado);
require(state != InstanceRegistry.InstanceState.DISABLED, "The instance is not supported");
if (isERC20) {
token.safeTransferFrom(msg.sender, address(this), _tornado.denomination());
}
_tornado.deposit{ value: msg.value }(_commitment);
emit EncryptedNote(msg.sender, _encryptedNote);
}
function withdraw(
ITornadoInstance _tornado,
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) public payable virtual {
(, , InstanceRegistry.InstanceState state, , ) = instanceRegistry.instances(_tornado);
require(state != InstanceRegistry.InstanceState.DISABLED, "The instance is not supported");
relayerRegistry.burn(msg.sender, _relayer, _tornado);
_tornado.withdraw{ value: msg.value }(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund);
}
/**
* @dev Sets `amount` allowance of `_spender` over the router's (this contract) tokens.
*/
function approveExactToken(
IERC20 _token,
address _spender,
uint256 _amount
) external onlyInstanceRegistry {
_token.safeApprove(_spender, _amount);
}
/**
* @notice Manually backup encrypted notes
*/
function backupNotes(bytes[] calldata _encryptedNotes) external virtual {
for (uint256 i = 0; i < _encryptedNotes.length; i++) {
emit EncryptedNote(msg.sender, _encryptedNotes[i]);
}
}
/// @dev Method to claim junk and accidentally sent tokens
function rescueTokens(
IERC20 _token,
address payable _to,
uint256 _amount
) external virtual onlyGovernance {
require(_to != address(0), "TORN: can not send to zero address");
if (_token == IERC20(0)) {
// for Ether
uint256 totalBalance = address(this).balance;
uint256 balance = Math.min(totalBalance, _amount);
_to.transfer(balance);
} else {
// any other erc20
uint256 totalBalance = _token.balanceOf(address(this));
uint256 balance = Math.min(totalBalance, _amount);
require(balance > 0, "TORN: trying to send 0 balance");
_token.safeTransfer(_to, balance);
}
}
}

View File

@ -0,0 +1,151 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/token/ERC20/SafeERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v3/proxy/Initializable.sol";
interface ITornadoVault {
function withdrawTorn(address recipient, uint256 amount) external;
}
interface ITornadoGovernance {
function lockedBalance(address account) external view returns (uint256);
function userVault() external view returns (ITornadoVault);
}
/**
* @notice This is the staking contract of the governance staking upgrade.
* This contract should hold the staked funds which are received upon relayer registration,
* and properly attribute rewards to addresses without security issues.
* @dev CONTRACT RISKS:
* - Relayer staked TORN at risk if contract is compromised.
*
*/
contract TornadoStakingRewards is Initializable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/// @notice 1e25
uint256 public immutable ratioConstant;
ITornadoGovernance public immutable Governance;
IERC20 public immutable torn;
address public immutable relayerRegistry;
/// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn
uint256 public accumulatedRewardPerTorn;
/// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim
mapping(address => uint256) public accumulatedRewardRateOnLastUpdate;
/// @notice notes down how much an account may claim
mapping(address => uint256) public accumulatedRewards;
event RewardsUpdated(address indexed account, uint256 rewards);
event RewardsClaimed(address indexed account, uint256 rewardsClaimed);
modifier onlyGovernance() {
require(msg.sender == address(Governance), "only governance");
_;
}
// Minor code change here we won't resolve the registry by ENS
constructor(address governanceAddress, address tornAddress, address _relayerRegistry) public {
Governance = ITornadoGovernance(governanceAddress);
torn = IERC20(tornAddress);
relayerRegistry = _relayerRegistry;
ratioConstant = IERC20(tornAddress).totalSupply();
}
/**
* @notice This function should safely send a user his rewards.
* @dev IMPORTANT FUNCTION:
* We know that rewards are going to be updated every time someone locks or unlocks
* so we know that this function can't be used to falsely increase the amount of
* lockedTorn by locking in governance and subsequently calling it.
* - set rewards to 0 greedily
*/
function getReward() external {
uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender));
rewards = rewards.add(accumulatedRewards[msg.sender]);
accumulatedRewards[msg.sender] = 0;
torn.safeTransfer(msg.sender, rewards);
emit RewardsClaimed(msg.sender, rewards);
}
/**
* @notice This function should increment the proper amount of rewards per torn for the contract
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values)
* @param amount amount to add to the rewards
*/
function addBurnRewards(uint256 amount) external {
require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized");
accumulatedRewardPerTorn = accumulatedRewardPerTorn.add(
amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault())))
);
}
/**
* @notice This function should allow governance to properly update the accumulated rewards rate for an account
* @param account address of account to update data for
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
*
*/
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand)
external
onlyGovernance
{
uint256 claimed = _updateReward(account, amountLockedBeforehand);
accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
}
/**
* @notice This function should allow governance rescue tokens from the staking rewards contract
*
*/
function withdrawTorn(uint256 amount) external onlyGovernance {
if (amount == type(uint256).max) amount = torn.balanceOf(address(this));
torn.safeTransfer(address(Governance), amount);
}
/**
* @notice This function should calculated the proper amount of rewards attributed to user since the last update
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25
* - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a very small reward
* @param account address of account to calculate rewards for
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
* @return claimed the rewards attributed to user since the last update
*/
function _updateReward(address account, uint256 amountLockedBeforehand)
private
returns (uint256 claimed)
{
if (amountLockedBeforehand != 0) {
claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
amountLockedBeforehand
).div(ratioConstant);
}
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
emit RewardsUpdated(account, claimed);
}
/**
* @notice This function should show a user his rewards.
* @param account address of account to calculate rewards for
*/
function checkReward(address account) external view returns (uint256 rewards) {
uint256 amountLocked = Governance.lockedBalance(account);
if (amountLocked != 0) {
rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
amountLocked
).div(ratioConstant);
}
rewards = rewards.add(accumulatedRewards[account]);
}
}

View File

@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts-v3/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);
}
}

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title The interface for the Uniswap V3 Factory
/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
interface IUniswapV3Factory {
/// @notice Emitted when the owner of the factory is changed
/// @param oldOwner The owner before the owner was changed
/// @param newOwner The owner after the owner was changed
event OwnerChanged(address indexed oldOwner, address indexed newOwner);
/// @notice Emitted when a pool is created
/// @param token0 The first token of the pool by address sort order
/// @param token1 The second token of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The minimum number of ticks between initialized ticks
/// @param pool The address of the created pool
event PoolCreated(
address indexed token0,
address indexed token1,
uint24 indexed fee,
int24 tickSpacing,
address pool
);
/// @notice Emitted when a new fee amount is enabled for pool creation via the factory
/// @param fee The enabled fee, denominated in hundredths of a bip
/// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee
event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);
/// @notice Returns the current owner of the factory
/// @dev Can be changed by the current owner via setOwner
/// @return The address of the factory owner
function owner() external view returns (address);
/// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled
/// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context
/// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee
/// @return The tick spacing
function feeAmountTickSpacing(uint24 fee) external view returns (int24);
/// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
/// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
/// @param tokenA The contract address of either token0 or token1
/// @param tokenB The contract address of the other token
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @return pool The pool address
function getPool(
address tokenA,
address tokenB,
uint24 fee
) external view returns (address pool);
/// @notice Creates a pool for the given two tokens and fee
/// @param tokenA One of the two tokens in the desired pool
/// @param tokenB The other of the two tokens in the desired pool
/// @param fee The desired fee for the pool
/// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
/// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
/// are invalid.
/// @return pool The address of the newly created pool
function createPool(
address tokenA,
address tokenB,
uint24 fee
) external returns (address pool);
/// @notice Updates the owner of the factory
/// @dev Must be called by the current owner
/// @param _owner The new owner of the factory
function setOwner(address _owner) external;
/// @notice Enables a fee amount with the given tickSpacing
/// @dev Fee amounts may never be removed once enabled
/// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
/// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount
function enableFeeAmount(uint24 fee, int24 tickSpacing) external;
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
import './pool/IUniswapV3PoolImmutables.sol';
import './pool/IUniswapV3PoolState.sol';
import './pool/IUniswapV3PoolDerivedState.sol';
import './pool/IUniswapV3PoolActions.sol';
import './pool/IUniswapV3PoolOwnerActions.sol';
import './pool/IUniswapV3PoolEvents.sol';
/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
IUniswapV3PoolImmutables,
IUniswapV3PoolState,
IUniswapV3PoolDerivedState,
IUniswapV3PoolActions,
IUniswapV3PoolOwnerActions,
IUniswapV3PoolEvents
{
}

View File

@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
/// @notice Sets the initial price for the pool
/// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
/// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
function initialize(uint160 sqrtPriceX96) external;
/// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
/// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
/// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
/// on tickLower, tickUpper, the amount of liquidity, and the current price.
/// @param recipient The address for which the liquidity will be created
/// @param tickLower The lower tick of the position in which to add liquidity
/// @param tickUpper The upper tick of the position in which to add liquidity
/// @param amount The amount of liquidity to mint
/// @param data Any data that should be passed through to the callback
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1);
/// @notice Collects tokens owed to a position
/// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
/// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
/// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
/// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
/// @param recipient The address which should receive the fees collected
/// @param tickLower The lower tick of the position for which to collect fees
/// @param tickUpper The upper tick of the position for which to collect fees
/// @param amount0Requested How much token0 should be withdrawn from the fees owed
/// @param amount1Requested How much token1 should be withdrawn from the fees owed
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
/// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
/// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
/// @dev Fees must be collected separately via a call to #collect
/// @param tickLower The lower tick of the position for which to burn liquidity
/// @param tickUpper The upper tick of the position for which to burn liquidity
/// @param amount How much liquidity to burn
/// @return amount0 The amount of token0 sent to the recipient
/// @return amount1 The amount of token1 sent to the recipient
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external returns (uint256 amount0, uint256 amount1);
/// @notice Swap token0 for token1, or token1 for token0
/// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
/// @param recipient The address to receive the output of the swap
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
/// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
/// with 0 amount{0,1} and sending the donation amount(s) from the callback
/// @param recipient The address which will receive the token0 and token1 amounts
/// @param amount0 The amount of token0 to send
/// @param amount1 The amount of token1 to send
/// @param data Any data to be passed through to the callback
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external;
/// @notice Increase the maximum number of price and liquidity observations that this pool will store
/// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
/// the input observationCardinalityNext.
/// @param observationCardinalityNext The desired minimum number of observations for the pool to store
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
/// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
/// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
/// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
/// you must call it with secondsAgos = [3600, 0].
/// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
/// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
/// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
/// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
/// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
/// timestamp
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
/// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
/// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
/// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
/// snapshot is taken and the second snapshot is taken.
/// @param tickLower The lower tick of the range
/// @param tickUpper The upper tick of the range
/// @return tickCumulativeInside The snapshot of the tick accumulator for the range
/// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
/// @return secondsInside The snapshot of seconds per liquidity for the range
function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
external
view
returns (
int56 tickCumulativeInside,
uint160 secondsPerLiquidityInsideX128,
uint32 secondsInside
);
}

View File

@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
/// @notice Emitted exactly once by a pool when #initialize is first called on the pool
/// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
/// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
/// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
event Initialize(uint160 sqrtPriceX96, int24 tick);
/// @notice Emitted when liquidity is minted for a given position
/// @param sender The address that minted the liquidity
/// @param owner The owner of the position and recipient of any minted liquidity
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity minted to the position range
/// @param amount0 How much token0 was required for the minted liquidity
/// @param amount1 How much token1 was required for the minted liquidity
event Mint(
address sender,
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint256 amount0,
uint256 amount1
);
/// @notice Emitted when fees are collected by the owner of a position
/// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
/// @param owner The owner of the position for which fees are collected
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount0 The amount of token0 fees collected
/// @param amount1 The amount of token1 fees collected
event Collect(
address indexed owner,
address recipient,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount0,
uint128 amount1
);
/// @notice Emitted when a position's liquidity is removed
/// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
/// @param owner The owner of the position for which liquidity is removed
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity to remove
/// @param amount0 The amount of token0 withdrawn
/// @param amount1 The amount of token1 withdrawn
event Burn(
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint256 amount0,
uint256 amount1
);
/// @notice Emitted by the pool for any swaps between token0 and token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the output of the swap
/// @param amount0 The delta of the token0 balance of the pool
/// @param amount1 The delta of the token1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of price of the pool after the swap
event Swap(
address indexed sender,
address indexed recipient,
int256 amount0,
int256 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick
);
/// @notice Emitted by the pool for any flashes of token0/token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the tokens from flash
/// @param amount0 The amount of token0 that was flashed
/// @param amount1 The amount of token1 that was flashed
/// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
/// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
event Flash(
address indexed sender,
address indexed recipient,
uint256 amount0,
uint256 amount1,
uint256 paid0,
uint256 paid1
);
/// @notice Emitted by the pool for increases to the number of observations that can be stored
/// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
/// just before a mint/swap/burn.
/// @param observationCardinalityNextOld The previous value of the next observation cardinality
/// @param observationCardinalityNextNew The updated value of the next observation cardinality
event IncreaseObservationCardinalityNext(
uint16 observationCardinalityNextOld,
uint16 observationCardinalityNextNew
);
/// @notice Emitted when the protocol fee is changed by the pool
/// @param feeProtocol0Old The previous value of the token0 protocol fee
/// @param feeProtocol1Old The previous value of the token1 protocol fee
/// @param feeProtocol0New The updated value of the token0 protocol fee
/// @param feeProtocol1New The updated value of the token1 protocol fee
event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);
/// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
/// @param sender The address that collects the protocol fees
/// @param recipient The address that receives the collected protocol fees
/// @param amount0 The amount of token0 protocol fees that is withdrawn
/// @param amount0 The amount of token1 protocol fees that is withdrawn
event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
/// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
/// @return The contract address
function factory() external view returns (address);
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);
/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);
/// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
/// @return The fee
function fee() external view returns (uint24);
/// @notice The pool tick spacing
/// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
/// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
/// This value is an int24 to avoid casting even though it is always positive.
/// @return The tick spacing
function tickSpacing() external view returns (int24);
/// @notice The maximum amount of position liquidity that can use any tick in the range
/// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
/// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
/// @return The max amount of liquidity per tick
function maxLiquidityPerTick() external view returns (uint128);
}

View File

@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
/// @notice Set the denominator of the protocol's % share of the fees
/// @param feeProtocol0 new protocol fee for token0 of the pool
/// @param feeProtocol1 new protocol fee for token1 of the pool
function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;
/// @notice Collect the protocol fee accrued to the pool
/// @param recipient The address to which collected protocol fees should be sent
/// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
/// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
/// @return amount0 The protocol fee collected in token0
/// @return amount1 The protocol fee collected in token1
function collectProtocol(
address recipient,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
}

View File

@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
/// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
/// when accessed externally.
/// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
/// tick The current tick of the pool, i.e. according to the last tick transition that was run.
/// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
/// boundary.
/// observationIndex The index of the last oracle observation that was written,
/// observationCardinality The current maximum number of observations stored in the pool,
/// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
/// feeProtocol The protocol fee for both tokens of the pool.
/// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
/// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
/// unlocked Whether the pool is currently locked to reentrancy
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
/// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
function feeGrowthGlobal0X128() external view returns (uint256);
/// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
function feeGrowthGlobal1X128() external view returns (uint256);
/// @notice The amounts of token0 and token1 that are owed to the protocol
/// @dev Protocol fees will never exceed uint128 max in either token
function protocolFees() external view returns (uint128 token0, uint128 token1);
/// @notice The currently in range liquidity available to the pool
/// @dev This value has no relationship to the total liquidity across all ticks
function liquidity() external view returns (uint128);
/// @notice Look up information about a specific tick in the pool
/// @param tick The tick to look up
/// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
/// tick upper,
/// liquidityNet how much liquidity changes when the pool price crosses the tick,
/// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
/// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
/// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
/// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
/// secondsOutside the seconds spent on the other side of the tick from the current tick,
/// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
/// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
/// In addition, these values are only relative and must be used only in comparison to previous snapshots for
/// a specific position.
function ticks(int24 tick)
external
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128,
int56 tickCumulativeOutside,
uint160 secondsPerLiquidityOutsideX128,
uint32 secondsOutside,
bool initialized
);
/// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
function tickBitmap(int16 wordPosition) external view returns (uint256);
/// @notice Returns the information about a position by the position's key
/// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
/// @return _liquidity The amount of liquidity in the position,
/// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
/// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
/// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
/// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
function positions(bytes32 key)
external
view
returns (
uint128 _liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
/// @notice Returns data about a specific observation index
/// @param index The element of the observations array to fetch
/// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
/// ago, rather than at a specific index in the array.
/// @return blockTimestamp The timestamp of the observation,
/// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
/// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
/// Returns initialized whether the observation has been initialized and the values are safe to use
function observations(uint256 index)
external
view
returns (
uint32 blockTimestamp,
int56 tickCumulative,
uint160 secondsPerLiquidityCumulativeX128,
bool initialized
);
}

View File

@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (type(uint256).max - denominator + 1) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.6.12;
/// @title Optimized overflow and underflow safe math operations
/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost
library LowGasSafeMath {
/// @notice Returns x + y, reverts if sum overflows uint256
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x);
}
/// @notice Returns x - y, reverts if underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x);
}
/// @notice Returns x * y, reverts if overflows
/// @param x The multiplicand
/// @param y The multiplier
/// @return z The product of x and y
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(x == 0 || (z = x * y) / x == y);
}
/// @notice Returns x + y, reverts if overflows or underflows
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x + y) >= x == (y >= 0));
}
/// @notice Returns x - y, reverts if overflows or underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x - y) <= x == (y >= 0));
}
}

View File

@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(int(MAX_TICK)), 'T');
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
// second inequality must be < because the price can never reach the price at the max tick
require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R');
uint256 ratio = uint256(sqrtPriceX96) << 32;
uint256 r = ratio;
uint256 msb = 0;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = ratio >> (msb - 127);
else r = ratio << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}

View File

@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
import '../../../v3-core/contracts/libraries/FullMath.sol';
import '../../../v3-core/contracts/libraries/TickMath.sol';
import '../../../v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '../../../v3-core/contracts/libraries/LowGasSafeMath.sol';
import './PoolAddress.sol';
/// @title Oracle library
/// @notice Provides functions to integrate with V3 pool oracle
library OracleLibrary {
/// @notice Fetches time-weighted average tick using Uniswap V3 oracle
/// @param pool Address of Uniswap V3 pool that we want to observe
/// @param period Number of seconds in the past to start calculating time-weighted average
/// @return timeWeightedAverageTick The time-weighted average tick from (block.timestamp - period) to block.timestamp
function consult(address pool, uint32 period) internal view returns (int24 timeWeightedAverageTick) {
require(period != 0, 'BP');
uint32[] memory secondAgos = new uint32[](2);
secondAgos[0] = period;
secondAgos[1] = 0;
(int56[] memory tickCumulatives, ) = IUniswapV3Pool(pool).observe(secondAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
timeWeightedAverageTick = int24(tickCumulativesDelta / int(uint256(period)));
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int(uint256(period)) != 0)) timeWeightedAverageTick--;
}
/// @notice Given a tick and a token amount, calculates the amount of token received in exchange
/// @param tick Tick value used to calculate the quote
/// @param baseAmount Amount of token to be converted
/// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
/// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
/// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
function getQuoteAtTick(
int24 tick,
uint128 baseAmount,
address baseToken,
address quoteToken
) internal pure returns (uint256 quoteAmount) {
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
// Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
if (sqrtRatioX96 <= type(uint128).max) {
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
quoteAmount = baseToken < quoteToken
? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
: FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
} else {
uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
quoteAmount = baseToken < quoteToken
? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
: FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
}
}
}

View File

@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
/// @notice The identifying key of the pool
struct PoolKey {
address token0;
address token1;
uint24 fee;
}
/// @notice Returns PoolKey: the ordered tokens with the matched fee levels
/// @param tokenA The first token of a pool, unsorted
/// @param tokenB The second token of a pool, unsorted
/// @param fee The fee level of the pool
/// @return Poolkey The pool details with ordered token0 and token1 assignments
function getPoolKey(
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (PoolKey memory) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
}
/// @notice Deterministically computes the pool address given the factory and PoolKey
/// @param factory The Uniswap V3 factory contract address
/// @param key The PoolKey
/// @return pool The contract address of the V3 pool
function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) {
require(key.token0 < key.token1);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}

View File

@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
interface ITornadoInstance {
function token() external view returns (address);
function denomination() external view returns (uint256);
function deposit(bytes32 commitment) external payable;
function withdraw(
bytes calldata proof,
bytes32 root,
bytes32 nullifierHash,
address payable recipient,
address payable relayer,
uint256 fee,
uint256 refund
) external payable;
}

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.24 <0.7.0;
/**
* @title Initializable
*
* @dev Helper contract to support initializer functions. To use it, replace
* the constructor with a function that has the `initializer` modifier.
* WARNING: Unlike constructors, initializer functions must be manually
* invoked. This applies both to deploying an Initializable contract, as well
* as extending an Initializable contract via inheritance.
* WARNING: When used with inheritance, manual care must be taken to not invoke
* a parent initializer twice, or ensure that all initializers are idempotent,
* because this is not dealt with automatically as with constructors.
*/
contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private initializing;
/**
* @dev Modifier to use in the initializer function of a contract.
*/
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool isTopLevelCall = !initializing;
if (isTopLevelCall) {
initializing = true;
initialized = true;
}
_;
if (isTopLevelCall) {
initializing = false;
}
}
/// @dev Returns true if and only if the function is running in the constructor
function isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
address self = address(this);
uint256 cs;
assembly { cs := extcodesize(self) }
return cs == 0;
}
// Reserved storage space to allow for layout changes in the future.
uint256[50] private ______gap;
}

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
/**
* @dev Ownable contract that wouldn't use any existing storage slots
*/
library OwnableLibrary {
struct OwnerStorage {
address owner;
}
bytes32 private constant OwnerStorageLocation = keccak256(abi.encode(uint256(keccak256("tornado.storage.owner"))));
function _getOwnerStorage() private pure returns (OwnerStorage storage pointer) {
bytes32 slot = OwnerStorageLocation;
assembly {
pointer_slot := slot
}
}
function setOwner(address _owner) internal {
_getOwnerStorage().owner = _owner;
}
function getOwner() internal view returns (address owner) {
owner = _getOwnerStorage().owner;
}
}

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { OracleLibrary } from "../Uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
import { IUniswapV3Factory } from "../Uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import { LowGasSafeMath } from "../Uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
interface IERC20Decimals {
function decimals() external view returns (uint8);
}
library UniswapV3OracleHelper {
using LowGasSafeMath for uint256;
IUniswapV3Factory internal constant UniswapV3Factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984);
address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint256 internal constant RATIO_DIVIDER = 1e18;
/**
* @notice This function should return the price of baseToken in quoteToken, as in: quote/base (WETH/TORN)
* @dev uses the Uniswap written OracleLibrary "getQuoteAtTick", does not call external libraries,
* uses decimals() for the correct power of 10
* @param baseToken token which will be denominated in quote token
* @param quoteToken token in which price will be denominated
* @param fee the uniswap pool fee, pools have different fees so this is a pool selector for our usecase
* @param period the amount of seconds we are going to look into the past for the new token price
* @return returns the price of baseToken in quoteToken
* */
function getPriceOfTokenInToken(
address baseToken,
address quoteToken,
uint24 fee,
uint32 period
) internal view returns (uint256) {
uint128 base = uint128(10)**uint128(IERC20Decimals(quoteToken).decimals());
if (baseToken == quoteToken) return base;
else
return
OracleLibrary.getQuoteAtTick(
OracleLibrary.consult(UniswapV3Factory.getPool(baseToken, quoteToken, fee), period),
base,
baseToken,
quoteToken
);
}
/**
* @notice This function should return the price of token in WETH
* @dev simply feeds WETH in to the above function
* @param token token which will be denominated in WETH
* @param fee the uniswap pool fee, pools have different fees so this is a pool selector for our usecase
* @param period the amount of seconds we are going to look into the past for the new token price
* @return returns the price of token in WETH
* */
function getPriceOfTokenInWETH(
address token,
uint24 fee,
uint32 period
) internal view returns (uint256) {
return getPriceOfTokenInToken(token, WETH, fee, period);
}
/**
* @notice This function should return the price of WETH in token
* @dev simply feeds WETH into getPriceOfTokenInToken
* @param token token which WETH will be denominated in
* @param fee the uniswap pool fee, pools have different fees so this is a pool selector for our usecase
* @param period the amount of seconds we are going to look into the past for the new token price
* @return returns the price of token in WETH
* */
function getPriceOfWETHInToken(
address token,
uint24 fee,
uint32 period
) internal view returns (uint256) {
return getPriceOfTokenInToken(WETH, token, fee, period);
}
/**
* @notice This function returns the price of token[0] in token[1], but more precisely and importantly the price ratio of the tokens in WETH
* @dev this is done as to always have good prices due to WETH-token pools mostly always having the most liquidity
* @param tokens array of tokens to get ratio for
* @param fees the uniswap pool FEES, since these are two independent tokens
* @param period the amount of seconds we are going to look into the past for the new token price
* @return returns the price of token[0] in token[1]
* */
function getPriceRatioOfTokens(
address[2] memory tokens,
uint24[2] memory fees,
uint32 period
) internal view returns (uint256) {
return
getPriceOfTokenInWETH(tokens[0], fees[0], period).mul(RATIO_DIVIDER) / getPriceOfTokenInWETH(tokens[1], fees[1], period);
}
}

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Configuration {
/// @notice Time delay between proposal vote completion and its execution
uint256 public EXECUTION_DELAY;
/// @notice Time before a passed proposal is considered expired
uint256 public EXECUTION_EXPIRATION;
/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
uint256 public QUORUM_VOTES;
/// @notice The number of votes required in order for a voter to become a proposer
uint256 public PROPOSAL_THRESHOLD;
/// @notice The delay before voting on a proposal may take place, once proposed
/// It is needed to prevent reorg attacks that replace the proposal
uint256 public VOTING_DELAY;
/// @notice The duration of voting on a proposal
uint256 public VOTING_PERIOD;
/// @notice If the outcome of a proposal changes during CLOSING_PERIOD, the vote will be extended by VOTE_EXTEND_TIME (no more than once)
uint256 public CLOSING_PERIOD;
/// @notice If the outcome of a proposal changes during CLOSING_PERIOD, the vote will be extended by VOTE_EXTEND_TIME (no more than once)
uint256 public VOTE_EXTEND_TIME;
modifier onlySelf() {
require(msg.sender == address(this), "Governance: unauthorized");
_;
}
function _initializeConfiguration() internal {
EXECUTION_DELAY = 2 days;
EXECUTION_EXPIRATION = 3 days;
QUORUM_VOTES = 25_000e18; // 0.25% of TORN
PROPOSAL_THRESHOLD = 1000e18; // 0.01% of TORN
VOTING_DELAY = 75 seconds;
VOTING_PERIOD = 3 days;
CLOSING_PERIOD = 1 hours;
VOTE_EXTEND_TIME = 6 hours;
}
function setExecutionDelay(uint256 executionDelay) external onlySelf {
EXECUTION_DELAY = executionDelay;
}
function setExecutionExpiration(uint256 executionExpiration) external onlySelf {
EXECUTION_EXPIRATION = executionExpiration;
}
function setQuorumVotes(uint256 quorumVotes) external onlySelf {
QUORUM_VOTES = quorumVotes;
}
function setProposalThreshold(uint256 proposalThreshold) external onlySelf {
PROPOSAL_THRESHOLD = proposalThreshold;
}
function setVotingDelay(uint256 votingDelay) external onlySelf {
VOTING_DELAY = votingDelay;
}
function setVotingPeriod(uint256 votingPeriod) external onlySelf {
VOTING_PERIOD = votingPeriod;
}
function setClosingPeriod(uint256 closingPeriod) external onlySelf {
CLOSING_PERIOD = closingPeriod;
}
function setVoteExtendTime(uint256 voteExtendTime) external onlySelf {
// VOTE_EXTEND_TIME should be less EXECUTION_DELAY to prevent double voting
require(voteExtendTime < EXECUTION_DELAY, "Governance: incorrect voteExtendTime");
VOTE_EXTEND_TIME = voteExtendTime;
}
}

View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
abstract contract Core {
/// @notice Locked token balance for each account
mapping(address => uint256) public lockedBalance;
}

View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import { Core } from "./Core.sol";
abstract contract Delegation is Core {
/// @notice Delegatee records
mapping(address => address) public delegatedTo;
event Delegated(address indexed account, address indexed to);
event Undelegated(address indexed account, address indexed from);
function delegate(address to) external {
address previous = delegatedTo[msg.sender];
require(
to != msg.sender && to != address(this) && to != address(0) && to != previous,
"Governance: invalid delegatee"
);
if (previous != address(0)) {
emit Undelegated(msg.sender, previous);
}
delegatedTo[msg.sender] = to;
emit Delegated(msg.sender, to);
}
function undelegate() external {
address previous = delegatedTo[msg.sender];
require(previous != address(0), "Governance: tokens are already undelegated");
delegatedTo[msg.sender] = address(0);
emit Undelegated(msg.sender, previous);
}
function proposeByDelegate(address from, address target, string memory description)
external
returns (uint256)
{
require(delegatedTo[from] == msg.sender, "Governance: not authorized");
return _propose(from, target, description);
}
function _propose(address proposer, address target, string memory description)
internal
virtual
returns (uint256);
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support) external virtual {
for (uint256 i = 0; i < from.length; i++) {
require(delegatedTo[from[i]] == msg.sender, "Governance: not authorized");
_castVote(from[i], proposalId, support);
}
if (lockedBalance[msg.sender] > 0) {
_castVote(msg.sender, proposalId, support);
}
}
function _castVote(address voter, uint256 proposalId, bool support) internal virtual;
}

View File

@ -0,0 +1,294 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
import { Initializable } from "../libraries/Initializable.sol";
import { Address } from "@openzeppelin/contracts-v3/utils/Address.sol";
import { TORN } from "../TORN/TORN.sol";
import { Delegation } from "./Delegation.sol";
import { Configuration } from "./Configuration.sol";
contract Governance is Initializable, Configuration, Delegation {
using SafeMath for uint256;
/// @notice Possible states that a proposal may be in
enum ProposalState {
Pending,
Active,
Defeated,
Timelocked,
AwaitingExecution,
Executed,
Expired
}
struct Proposal {
// Creator of the proposal
address proposer;
// target addresses for the call to be made
address target;
// The block at which voting begins
uint256 startTime;
// The block at which voting ends: votes must be cast prior to this block
uint256 endTime;
// Current number of votes in favor of this proposal
uint256 forVotes;
// Current number of votes in opposition to this proposal
uint256 againstVotes;
// Flag marking whether the proposal has been executed
bool executed;
// Flag marking whether the proposal voting time has been extended
// Voting time can be extended once, if the proposal outcome has changed during CLOSING_PERIOD
bool extended;
// Receipts of ballots for the entire set of voters
mapping(address => Receipt) receipts;
}
/// @notice Ballot receipt record for a voter
struct Receipt {
// Whether or not a vote has been cast
bool hasVoted;
// Whether or not the voter supports the proposal
bool support;
// The number of votes the voter had, which were cast
uint256 votes;
}
/// @notice The official record of all proposals ever proposed
Proposal[] public proposals;
/// @notice The latest proposal for each proposer
mapping(address => uint256) public latestProposalIds;
/// @notice Timestamp when a user can withdraw tokens
mapping(address => uint256) public canWithdrawAfter;
TORN public torn;
/// @notice An event emitted when a new proposal is created
event ProposalCreated(
uint256 indexed id,
address indexed proposer,
address target,
uint256 startTime,
uint256 endTime,
string description
);
/// @notice An event emitted when a vote has been cast on a proposal
event Voted(uint256 indexed proposalId, address indexed voter, bool indexed support, uint256 votes);
/// @notice An event emitted when a proposal has been executed
event ProposalExecuted(uint256 indexed proposalId);
/// @notice Makes this instance inoperable to prevent selfdestruct attack
/// Proxy will still be able to properly initialize its storage
constructor() public initializer {
torn = TORN(0x000000000000000000000000000000000000dEaD);
_initializeConfiguration();
}
function initialize(address _torn) external initializer {
torn = TORN(_torn);
// Create a dummy proposal so that indexes start from 1
proposals.push(
Proposal({
proposer: address(this),
target: 0x000000000000000000000000000000000000dEaD,
startTime: 0,
endTime: 0,
forVotes: 0,
againstVotes: 0,
executed: true,
extended: false
})
);
_initializeConfiguration();
}
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
virtual
{
torn.permit(owner, address(this), amount, deadline, v, r, s);
_transferTokens(owner, amount);
}
function lockWithApproval(uint256 amount) public virtual {
_transferTokens(msg.sender, amount);
}
function unlock(uint256 amount) public virtual {
require(getBlockTimestamp() > canWithdrawAfter[msg.sender], "Governance: tokens are locked");
lockedBalance[msg.sender] = lockedBalance[msg.sender].sub(amount, "Governance: insufficient balance");
require(torn.transfer(msg.sender, amount), "TORN: transfer failed");
}
function propose(address target, string memory description) external returns (uint256) {
return _propose(msg.sender, target, description);
}
/**
* @notice Propose implementation
* @param proposer proposer address
* @param target smart contact address that will be executed as result of voting
* @param description description of the proposal
* @return the new proposal id
*/
function _propose(address proposer, address target, string memory description)
internal
virtual
override(Delegation)
returns (uint256)
{
uint256 votingPower = lockedBalance[proposer];
require(
votingPower >= PROPOSAL_THRESHOLD, "Governance::propose: proposer votes below proposal threshold"
);
// target should be a contract
require(Address.isContract(target), "Governance::propose: not a contract");
uint256 latestProposalId = latestProposalIds[proposer];
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
require(
proposersLatestProposalState != ProposalState.Active
&& proposersLatestProposalState != ProposalState.Pending,
"Governance::propose: one live proposal per proposer, found an already active proposal"
);
}
uint256 startTime = getBlockTimestamp().add(VOTING_DELAY);
uint256 endTime = startTime.add(VOTING_PERIOD);
Proposal memory newProposal = Proposal({
proposer: proposer,
target: target,
startTime: startTime,
endTime: endTime,
forVotes: 0,
againstVotes: 0,
executed: false,
extended: false
});
proposals.push(newProposal);
uint256 proposalId = proposalCount();
latestProposalIds[newProposal.proposer] = proposalId;
_lockTokens(proposer, endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY));
emit ProposalCreated(proposalId, proposer, target, startTime, endTime, description);
return proposalId;
}
function execute(uint256 proposalId) public payable virtual {
require(
state(proposalId) == ProposalState.AwaitingExecution,
"Governance::execute: invalid proposal state"
);
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
address target = proposal.target;
require(Address.isContract(target), "Governance::execute: not a contract");
(bool success, bytes memory data) = target.delegatecall(abi.encodeWithSignature("executeProposal()"));
if (!success) {
if (data.length > 0) {
revert(string(data));
} else {
revert("Proposal execution failed");
}
}
emit ProposalExecuted(proposalId);
}
function castVote(uint256 proposalId, bool support) external virtual {
_castVote(msg.sender, proposalId, support);
}
function _castVote(address voter, uint256 proposalId, bool support) internal override(Delegation) {
require(state(proposalId) == ProposalState.Active, "Governance::_castVote: voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
bool beforeVotingState = proposal.forVotes <= proposal.againstVotes;
uint256 votes = lockedBalance[voter];
require(votes > 0, "Governance: balance is 0");
if (receipt.hasVoted) {
if (receipt.support) {
proposal.forVotes = proposal.forVotes.sub(receipt.votes);
} else {
proposal.againstVotes = proposal.againstVotes.sub(receipt.votes);
}
}
if (support) {
proposal.forVotes = proposal.forVotes.add(votes);
} else {
proposal.againstVotes = proposal.againstVotes.add(votes);
}
if (!proposal.extended && proposal.endTime.sub(getBlockTimestamp()) < CLOSING_PERIOD) {
bool afterVotingState = proposal.forVotes <= proposal.againstVotes;
if (beforeVotingState != afterVotingState) {
proposal.extended = true;
proposal.endTime = proposal.endTime.add(VOTE_EXTEND_TIME);
}
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
_lockTokens(
voter, proposal.endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY)
);
emit Voted(proposalId, voter, support, votes);
}
function _lockTokens(address owner, uint256 timestamp) internal {
if (timestamp > canWithdrawAfter[owner]) {
canWithdrawAfter[owner] = timestamp;
}
}
function _transferTokens(address owner, uint256 amount) internal virtual {
require(torn.transferFrom(owner, address(this), amount), "TORN: transferFrom failed");
lockedBalance[owner] = lockedBalance[owner].add(amount);
}
function getReceipt(uint256 proposalId, address voter) public view returns (Receipt memory) {
return proposals[proposalId].receipts[voter];
}
function state(uint256 proposalId) public virtual view returns (ProposalState) {
require(proposalId <= proposalCount() && proposalId > 0, "Governance::state: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
if (getBlockTimestamp() <= proposal.startTime) {
return ProposalState.Pending;
} else if (getBlockTimestamp() <= proposal.endTime) {
return ProposalState.Active;
} else if (
proposal.forVotes <= proposal.againstVotes
|| proposal.forVotes + proposal.againstVotes < QUORUM_VOTES
) {
return ProposalState.Defeated;
} else if (proposal.executed) {
return ProposalState.Executed;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY).add(EXECUTION_EXPIRATION)) {
return ProposalState.Expired;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY)) {
return ProposalState.AwaitingExecution;
} else {
return ProposalState.Timelocked;
}
}
function proposalCount() public view returns (uint256) {
return proposals.length - 1;
}
function getBlockTimestamp() internal view virtual returns (uint256) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Dummy {
uint256 public value;
string public text;
function initialize() public {
value = 1;
text = "dummy";
}
// function update(address _impl) public {
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
// // MyProxy(address(this)).upgradeTo(_impl);
// }
}
contract DummySecond {
uint256 public value;
string public text;
function initialize() public {
value = 2;
text = "dummy2";
}
// function update(address _impl) public {
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
// }
}

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../Governance.sol";
contract MockGovernance is Governance {
uint256 public time = block.timestamp;
function setTimestamp(uint256 time_) public {
time = time_;
}
function getBlockTimestamp() internal view override returns (uint256) {
// solium-disable-next-line security/no-block-members
return time;
}
function setTorn(address torna) external {
torn = TORN(torna);
}
/**
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
**/
}

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./Dummy.sol";
contract Proposal {
// bytes32 public constant WEIRD = keccak256("Hey Proposal");
// uint256 public someValue = 111;
// Dummy public dummyInstance;
event Debug(address output);
function executeProposal() public {
// someValue = 321;
Dummy dummyInstance = new Dummy();
dummyInstance.initialize();
emit Debug(address(dummyInstance));
}
}

View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface IGovernance {
function setExecutionDelay(uint256 delay) external;
}
contract ProposalStateChangeGovernance {
function executeProposal() public {
IGovernance(address(this)).setExecutionDelay(3 days);
}
}

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "./MockGovernance.sol";
interface IProxy {
function upgradeTo(address newImplementation) external;
}
contract NewImplementation is MockGovernance {
uint256 public newVariable;
event Overriden(uint256 x);
function execute(uint256 proposalId) public payable override {
newVariable = 999;
emit Overriden(proposalId);
}
}
contract ProposalUpgrade {
address public immutable newLogic;
constructor(address _newLogic) public {
newLogic = _newLogic;
}
function executeProposal() public {
IProxy(address(this)).upgradeTo(newLogic);
}
}

View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
interface IGasCompensationVault {
function compensateGas(address recipient, uint256 gasAmount) external;
function withdrawToGovernance(uint256 amount) external;
}
/**
* @notice This abstract contract is used to add gas compensation functionality to a contract.
*
*/
abstract contract GasCompensator {
using SafeMath for uint256;
/// @notice this vault is necessary for the gas compensation functionality to work
IGasCompensationVault public immutable gasCompensationVault;
constructor(address _gasCompensationVault) public {
gasCompensationVault = IGasCompensationVault(_gasCompensationVault);
}
/**
* @notice modifier which should compensate gas to account if eligible
* @dev Consider reentrancy, repeated calling of the function being compensated, eligibility.
* @param account address to be compensated
* @param eligible if the account is eligible for compensations or not
* @param extra extra amount in gas to be compensated, will be multiplied by basefee
*
*/
modifier gasCompensation(address account, bool eligible, uint256 extra) {
if (eligible) {
uint256 startGas = gasleft();
_;
uint256 gasToCompensate = startGas.sub(gasleft()).add(extra).add(10e3);
gasCompensationVault.compensateGas(account, gasToCompensate);
} else {
_;
}
}
/**
* @notice inheritable unimplemented function to withdraw ether from the vault
*
*/
function withdrawFromHelper(uint256 amount) external virtual;
/**
* @notice inheritable unimplemented function to deposit ether into the vault
*
*/
function setGasCompensations(uint256 _gasCompensationsLimit) external virtual;
}

View File

@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { GovernanceVaultUpgrade } from "./GovernanceVaultUpgrade.sol";
import { GasCompensator } from "./GasCompensator.sol";
import { Math } from "@openzeppelin/contracts-v3/math/Math.sol";
/**
* @notice This contract should upgrade governance to be able to compensate gas for certain actions.
* These actions are set to castVote, castDelegatedVote in this contract.
*
*/
contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
/**
* @notice constructor
* @param _gasCompLogic gas compensation vault address
* @param _userVault tornado vault address
*
*/
constructor(address _gasCompLogic, address _userVault)
public
GovernanceVaultUpgrade(_userVault)
GasCompensator(_gasCompLogic)
{ }
/// @notice check that msg.sender is multisig
modifier onlyMultisig() {
require(msg.sender == returnMultisigAddress(), "only multisig");
_;
}
/**
* @notice receive ether function, does nothing but receive ether
*
*/
receive() external payable { }
/**
* @notice function to add a certain amount of ether for gas compensations
* @dev send ether is used in the logic as we don't expect multisig to make a reentrancy attack on governance
* @param gasCompensationsLimit the amount of gas to be compensated
*
*/
function setGasCompensations(uint256 gasCompensationsLimit) external virtual override onlyMultisig {
require(
payable(address(gasCompensationVault)).send(
Math.min(gasCompensationsLimit, address(this).balance)
)
);
}
/**
* @notice function to withdraw funds from the gas compensator
* @dev send ether is used in the logic as we don't expect multisig to make a reentrancy attack on governance
* @param amount the amount of ether to withdraw
*
*/
function withdrawFromHelper(uint256 amount) external virtual override onlyMultisig {
gasCompensationVault.withdrawToGovernance(amount);
}
/**
* @notice function to cast callers votes on a proposal
* @dev IMPORTANT: This function uses the gasCompensation modifier.
* as such this function can trigger a payable fallback.
* It is not possible to vote without revert more than once,
* without hasAccountVoted being true, eliminating gas refunds in this case.
* Gas compensation is also using the low level send(), forwarding 23000 gas
* as to disallow further logic execution above that threshold.
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
*
*/
function castVote(uint256 proposalId, bool support)
external
virtual
override
gasCompensation(
msg.sender,
!hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId),
(msg.sender == tx.origin ? 21e3 : 0)
)
{
_castVote(msg.sender, proposalId, support);
}
/**
* @notice function to cast callers votes and votes delegated to the caller
* @param from array of addresses that should have delegated to voter
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
*
*/
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support)
external
virtual
override
{
require(from.length > 0, "Can not be empty");
_castDelegatedVote(
from,
proposalId,
support,
!hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId)
);
}
/// @notice checker for success on deployment
/// @return returns precise version of governance
function version() external pure virtual override returns (string memory) {
return "2.lottery-and-gas-upgrade";
}
/**
* @notice function to check if quorum has been reached on a given proposal
* @param proposalId id of proposal
* @return true if quorum has been reached
*
*/
function checkIfQuorumReached(uint256 proposalId) public view returns (bool) {
return (proposals[proposalId].forVotes + proposals[proposalId].againstVotes >= QUORUM_VOTES);
}
/**
* @notice function to check if account has voted on a proposal
* @param proposalId id of proposal account should have voted on
* @param account address of the account
* @return true if acc has voted
*
*/
function hasAccountVoted(uint256 proposalId, address account) public view returns (bool) {
return proposals[proposalId].receipts[account].hasVoted;
}
/**
* @notice function to retrieve the multisig address
* @dev reasoning: if multisig changes we need governance to approve the next multisig address,
* so simply inherit in a governance upgrade from this function and set the new address
* @return the multisig address
*
*/
function returnMultisigAddress() public pure virtual returns (address) {
return 0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4;
}
/**
* @notice This should handle the logic of the external function
* @dev IMPORTANT: This function uses the gasCompensation modifier.
* as such this function can trigger a payable fallback.
* It is not possible to vote without revert more than once,
* without hasAccountVoted being true, eliminating gas refunds in this case.
* Gas compensation is also using the low level send(), forwarding 23000 gas
* as to disallow further logic execution above that threshold.
* @param from array of addresses that should have delegated to voter
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
* @param gasCompensated true if gas should be compensated (given all internal checks pass)
*
*/
function _castDelegatedVote(address[] memory from, uint256 proposalId, bool support, bool gasCompensated)
internal
gasCompensation(msg.sender, gasCompensated, (msg.sender == tx.origin ? 21e3 : 0))
{
for (uint256 i = 0; i < from.length; i++) {
address delegator = from[i];
require(
delegatedTo[delegator] == msg.sender || delegator == msg.sender, "Governance: not authorized"
);
require(!gasCompensated || !hasAccountVoted(proposalId, delegator), "Governance: voted already");
_castVote(delegator, proposalId, support);
}
}
}

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Governance } from "../v1/Governance.sol";
import { SafeMath } from "@openzeppelin/contracts-v3/math/SafeMath.sol";
import { ITornadoVault } from "./interfaces/ITornadoVault.sol";
/// @title Version 2 Governance contract of the tornado.cash governance
contract GovernanceVaultUpgrade is Governance {
using SafeMath for uint256;
// vault which stores user TORN
ITornadoVault public immutable userVault;
// call Governance v1 constructor
constructor(address _userVault) public Governance() {
userVault = ITornadoVault(_userVault);
}
/// @notice Withdraws TORN from governance if conditions permit
/// @param amount the amount of TORN to withdraw
function unlock(uint256 amount) public virtual override {
require(getBlockTimestamp() > canWithdrawAfter[msg.sender], "Governance: tokens are locked");
lockedBalance[msg.sender] = lockedBalance[msg.sender].sub(amount, "Governance: insufficient balance");
userVault.withdrawTorn(msg.sender, amount);
}
/// @notice checker for success on deployment
/// @return returns precise version of governance
function version() external pure virtual returns (string memory) {
return "2.vault-migration";
}
/// @notice transfers tokens from the contract to the vault, withdrawals are unlock()
/// @param owner account/contract which (this) spender will send to the user vault
/// @param amount amount which spender will send to the user vault
function _transferTokens(address owner, uint256 amount) internal virtual override {
require(torn.transferFrom(owner, address(userVault), amount), "TORN: transferFrom failed");
lockedBalance[owner] = lockedBalance[owner].add(amount);
}
}

View File

@ -0,0 +1,29 @@
# Tornado Governance Changes
Governance proposal [repo](https://github.com/peppersec/tornado-vault-and-gas-proposal).
## Governance Vault Upgrade (GovernanceVaultUpgrade.sol)
`GovernanceVaultUpgrade` is the first major upgrade to tornado governance. This upgrade introduces new logic which is used to communicate with `TornVault` from the governance contract. The motivation behind this upgrade:
- split DAO member locked TORN from vesting locked TORN.
- block Governance from being able to interact with user TORN.
To solve point 1 of the formerly stated problems, and to reduce the logic bloat of the lock and unlock functionalities, we have opted for calculating the amount of user TORN locked in the governance contract. The calculations and explanations may be found [here](https://github.com/h-ivor/tornado-lottery-period/blob/final_with_auction/scripts/balance_estimation.md).
### Additions and changes
| Function/variable signature | is addition or change? | describe significance |
| ---------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `_transferTokens(address,uint256)` | change | instead of transferring to the governance contract, funds are now transferred to the torn vault with a `transferFrom` call, this has an effect on both the `lock` and `lockWithApproval` function |
| `unlock(uint256)` | change | unlock now triggers `withdrawTorn(address,uint256)` within the vault which reverts on an unsuccessful transfer (safeTransfer) |
| `version` | addition | tells current version of governance contract |
| `address immutable userVault` | addition | address of the deployed vault |
### Tornado Vault (TornadoVault.sol)
The compliment to the above upgrade. Stores user TORN, does not keep records of it. Serves exclusively for deposits and withdrawals. Works in effect as personal store of TORN for a user with the balance being user for voting. Locking mechanisms are still in effect.
| Function/variable signature | describe significance |
| ------------------------------- | --------------------------------------------------- |
| `withdrawTorn(address,uint256)` | used for withdrawing TORN balance to users' account |

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
interface ITornadoVault {
function withdrawTorn(address recipient, uint256 amount) external;
}

View File

@ -0,0 +1,59 @@
// 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);
}
}

View File

@ -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.

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface ITornadoStakingRewards {
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external;
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts-v3/proxy/TransparentUpgradeableProxy.sol";
/**
* @dev TransparentUpgradeableProxy where admin is allowed to call implementation methods.
*/
contract AdminUpgradeableProxy is TransparentUpgradeableProxy {
/**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/
constructor(address _logic, address _admin, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, _admin, _data)
{ }
/**
* @dev Override to allow admin access the fallback function.
*/
function _beforeFallback() internal override { }
}

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Governance } from "../v1/Governance.sol";
import { GovernanceStakingUpgrade } from "../v3-relayer-registry/GovernanceStakingUpgrade.sol";
contract GovernanceExploitPatchUpgrade is GovernanceStakingUpgrade {
mapping(uint256 => bytes32) public proposalCodehashes;
constructor(address stakingRewardsAddress, address gasCompLogic, address userVaultAddress)
public
GovernanceStakingUpgrade(stakingRewardsAddress, gasCompLogic, userVaultAddress)
{ }
/// @notice Return the version of the contract
function version() external pure virtual override returns (string memory) {
return "4.patch-exploit";
}
/**
* @notice Execute a proposal
* @dev This upgrade should protect against Metamorphic contracts by comparing the proposal's extcodehash with a stored one
* @param proposalId The proposal's ID
*/
function execute(uint256 proposalId) public payable virtual override(Governance) {
require(msg.sender != address(this), "Governance::propose: pseudo-external function");
Proposal storage proposal = proposals[proposalId];
address target = proposal.target;
bytes32 proposalCodehash;
assembly {
proposalCodehash := extcodehash(target)
}
require(
proposalCodehash == proposalCodehashes[proposalId],
"Governance::propose: metamorphic contracts not allowed"
);
super.execute(proposalId);
}
/**
* @notice Internal function called from propoese
* @dev This should store the extcodehash of the proposal contract
* @param proposer proposer address
* @param target smart contact address that will be executed as result of voting
* @param description description of the proposal
* @return proposalId new proposal id
*/
function _propose(address proposer, address target, string memory description)
internal
virtual
override(Governance)
returns (uint256 proposalId)
{
// Implies all former predicates were valid
proposalId = super._propose(proposer, target, description);
bytes32 proposalCodehash;
assembly {
proposalCodehash := extcodehash(target)
}
proposalCodehashes[proposalId] = proposalCodehash;
}
}

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
import { LoopbackProxy } from "../LoopbackProxy.sol";
import { AdminUpgradeableProxy } from "./AdminUpgradeableProxy.sol";
import { GovernanceExploitPatchUpgrade } from "./GovernanceExploitPatchUpgrade.sol";
import { TornadoStakingRewards } from "../TornadoStakingRewards.sol";
import { RelayerRegistry } from "../RelayerRegistry.sol";
/**
* @notice Proposal which should patch governance against the metamorphic contract replacement vulnerability and also fix several issues which have appeared as a result of the attack.
*/
contract PatchProposal {
// Address of the old staking proxy
address public constant oldStakingProxyAddress = 0x2FC93484614a34f26F7970CBB94615bA109BB4bf;
// Address of the registry proxy
address public constant registryProxyAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
// Address of the gas compensation vault
address public constant gasCompensationVaultAddress = 0xFA4C1f3f7D5dd7c12a9Adb82Cd7dDA542E3d59ef;
// Address of the user vault
address public constant userVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185;
// Address of the governance proxy
address payable public constant governanceProxyAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
// Torn token
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
// The staking proxy (pointing to a new implementation (with same code)) that we've deployed
address public immutable deployedStakingProxyContractAddress;
// The registry implementation (with same code) that we've deployed
address public immutable deployedRelayerRegistryImplementationAddress;
constructor(
address _deployedStakingProxyContractAddress,
address _deployedRelayerRegistryImplementationAddress
) public {
deployedStakingProxyContractAddress = _deployedStakingProxyContractAddress;
deployedRelayerRegistryImplementationAddress = _deployedRelayerRegistryImplementationAddress;
}
/// @notice Function to execute the proposal.
function executeProposal() external {
// Get the old staking contract
TornadoStakingRewards oldStaking = TornadoStakingRewards(oldStakingProxyAddress);
// Get the small amount of TORN left
oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking)));
// Upgrade the registry proxy
AdminUpgradeableProxy(payable(registryProxyAddress)).upgradeTo(
deployedRelayerRegistryImplementationAddress
);
// Now upgrade the governance implementation to the vulnerability resistant one
LoopbackProxy(governanceProxyAddress).upgradeTo(
address(
new GovernanceExploitPatchUpgrade(
deployedStakingProxyContractAddress,
gasCompensationVaultAddress,
userVaultAddress
)
)
);
// Transfer TORN in compensation to the staking proxy
TORN.transfer(deployedStakingProxyContractAddress, 94_092 ether);
}
}

View File

@ -0,0 +1,46 @@
# Governance upgrade to patch exploit
### Major changes
1. Adding protection from metamorphic contracts to Governance voting process;
2. Redeploying Governance Staking proxy contract to nullify bugged rewards;
3. Return of tokens lost due to a bug in Governance Staking;
4. Redeploying Governance Staking logic contract and Relayer Registry logic contract to change the staking address to the current one.
### Requirements
- Rust ([Any system](https://doc.rust-lang.org/cargo/getting-started/installation.html))
- Foundryup ([Windows](https://github.com/altugbakan/foundryup-windows), [Linux](https://book.getfoundry.sh/getting-started/installation))
- Node 14 or higher ([Windows](https://github.com/coreybutler/nvm-windows), [Linux](https://github.com/nvm-sh/nvm))
### Installation
```text
git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch
cd proposal-22-governance-and-rewards-patch
npm install
```
### Testing
```text
npm run test
```
### Contracts info
The contracts can be currently found here:
- [GovernancePatchUpgrade](https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch/src/branch/main/src/v4-patch/GovernancePatchUpgrade.sol)
- [PatchProposal](https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch/src/branch/main/src/v4-patch/PatchProposal.sol)
Inlined version of the `RelayerRegistry` and `TornadoStakingRewards` are also used. Check the `diffs` folder to see how much they deviate from the deployed contract implementations.
For testing resistance against metamorphic contracts, we use the contracts provided by: https://github.com/0age/metamorphic.git
##### Deployed contracts
- [Governance logic (implementation) contract](https://etherscan.io/address/0xba178126c28f50ee60322a82f5ebcd6b3711e101#code)
- [Staking proxy contract](https://etherscan.io/address/0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29#code)
- [Staking logic (implementation) contract](https://etherscan.io/address/0xefbea4ec481c2467a1a94d94bc54f111f6a7345f#code)
- [Relayer Registry logic (implementation) contract](https://etherscan.io/address/0xe27b91724c55e950f68b394f33fa3b86693179c0#code)

View File

@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
interface IMetamorphicContractFactory {
function findMetamorphicContractAddress(bytes32 salt)
external
view
returns (address metamorphicContractAddress);
function deployMetamorphicContractFromExistingImplementation(
bytes32 salt,
address implementationContract,
bytes calldata metamorphicContractInitializationCalldata
) external payable returns (address metamorphicContractAddress);
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts-v3/token/ERC20/IERC20.sol";
contract InitialProposal {
event MockExecuted(uint256 num);
function executeProposal() external virtual {
emit MockExecuted(1);
}
function emergencyStop() public {
selfdestruct(payable(0));
}
}
contract MaliciousProposal is InitialProposal {
address public immutable deployer;
constructor() public {
deployer = msg.sender;
}
function executeProposal() external virtual override {
IERC20 torn = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
uint256 bal = torn.balanceOf(address(this));
torn.transfer(deployer, bal);
}
}

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Governance } from "../v1/Governance.sol";
import { GovernanceExploitPatchUpgrade } from "../v4-exploit-patch/GovernanceExploitPatchUpgrade.sol";
contract GovernanceProposalStateUpgrade is GovernanceExploitPatchUpgrade {
constructor(
address stakingRewardsAddress,
address gasCompLogic,
address userVaultAddress
) public GovernanceExploitPatchUpgrade(stakingRewardsAddress, gasCompLogic, userVaultAddress) {}
/// @notice Return the version of the contract
function version() external pure virtual override returns (string memory) {
return "5.proposal-state-patch";
}
function state(uint256 proposalId) public view virtual override returns (ProposalState) {
require(proposalId <= proposalCount() && proposalId > 0, "Governance::state: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
if (getBlockTimestamp() <= proposal.startTime) {
return ProposalState.Pending;
} else if (getBlockTimestamp() <= proposal.endTime) {
return ProposalState.Active;
} else if (proposal.executed) {
return ProposalState.Executed;
} else if (
proposal.forVotes <= proposal.againstVotes || proposal.forVotes + proposal.againstVotes < QUORUM_VOTES
) {
return ProposalState.Defeated;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY).add(EXECUTION_EXPIRATION)) {
return ProposalState.Expired;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY)) {
return ProposalState.AwaitingExecution;
} else {
return ProposalState.Timelocked;
}
}
}

View File

@ -0,0 +1,9 @@
# Proposal 46
Fix proposals state and update docs
Deploy: npx hardhat run --network mainnet script/deploy.js
Tests: npm run test
Dont forget to fill env file

35
hardhat.config.ts Normal file
View File

@ -0,0 +1,35 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "hardhat-storage-layout";
const config: HardhatUserConfig = {
defaultNetwork: 'hardhat',
solidity: {
compilers: [
{
version: '0.8.25',
settings: {
evmVersion: 'paris',
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: '0.6.12',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
networks: {
hardhat: {},
},
};
export default config;

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "tornado-contracts",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"lint": "eslint test --ext .ts --fix"
},
"files": [
"contracts",
"hardhat.config.ts",
"README.md",
"tsconfig.json",
"yarn.lock"
],
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-ignition": "^0.15.0",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@nomicfoundation/ignition-core": "^0.15.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"chai": "^4.2.0",
"eslint": "^8.57.0",
"ethers": "^6.4.0",
"hardhat": "^2.22.2",
"hardhat-gas-reporter": "^1.0.8",
"hardhat-storage-layout": "^0.1.7",
"solidity-coverage": "^0.8.1",
"ts-node": "^10.9.2",
"typechain": "^8.3.0",
"typescript": "^5.4.3"
},
"dependencies": {
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-v3": "npm:@openzeppelin/contracts@3.2.0-rc.0"
}
}

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["@types/node"]
},
"include": ["test/**/*"],
"exclude": ["node_modules"]
}

4475
yarn.lock Normal file

File diff suppressed because it is too large Load Diff