tornado-contracts/contracts/Unaudited/TornadoV2.sol

611 lines
20 KiB
Solidity
Raw Permalink Normal View History

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
2024-12-01 00:46:04 +00:00
import { IHasher } from '../Classic/interfaces/IHasher.sol';
import { IVerifier } from '../Classic/interfaces/IVerifier.sol';
import { IWETH } from '../Classic/interfaces/IWETH.sol';
import { IERC20, SafeERC20 } from "../Classic/libraries/SafeERC20.sol";
import { ReentrancyGuard } from '../Classic/libraries/ReentrancyGuard.sol';
import { IVault } from './interfaces/IVault.sol';
import { ISignatureTransfer } from './interfaces/ISignatureTransfer.sol';
import { ParseSignature } from './libraries/ParseSignature.sol';
/**
* @notice (WIP) Singleton Tornado Cash contract
* @author tornadocontrib.eth
*/
contract TornadoV2 is ReentrancyGuard {
using SafeERC20 for IERC20;
enum SignatureType {
PERMIT,
PERMIT2
}
struct PermitCommitments {
bytes32 instancesHash;
bytes32 commitmentsHash;
}
bytes public constant COMMITMENT_TYPE = "PermitCommitments(bytes32 instancesHash,bytes32 commitmentsHash)";
bytes32 public constant COMMITMENT_TYPEHASH = keccak256(bytes(COMMITMENT_TYPE));
string public constant WITNESS_TYPE_STRING = string(abi.encodePacked("PermitCommitments witness)", COMMITMENT_TYPE, "TokenPermissions(address token,uint256 amount)"));
// https://docs.uniswap.org/contracts/v3/reference/deployments/ethereum-deployments
ISignatureTransfer public constant permit2 = ISignatureTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
uint32 public constant ROOT_HISTORY_SIZE = 30;
uint16 public constant REVISION = 0;
IWETH public weth;
IHasher public hasher;
IVerifier public verifier;
uint32 public levels;
address public feeTo;
uint16 public constant feeRate = 30;
event Deposit(
uint256 indexed id,
address from,
bytes32 indexed commitment,
uint32 leafIndex,
uint256 timestamp
);
event Withdrawal(
uint256 indexed id,
address to,
bytes32 nullifierHash,
address indexed relayer,
uint256 fee,
uint256 timestamp
);
struct InstanceStorage {
mapping(uint256 => bytes32) filledSubtrees;
mapping(uint256 => bytes32) zeros;
mapping(uint256 => bytes32) roots;
uint32 currentRootIndex;
uint32 nextIndex;
IERC20 token;
uint256 denomination;
// total deposits - total withdrawals
uint32 delta;
uint256 rewards;
IVault vault;
}
struct InstanceView {
uint32 currentRootIndex;
uint32 nextIndex;
IERC20 token;
bool native;
uint256 denomination;
// total deposits - total withdrawals
uint32 delta;
uint256 rewards;
IVault vault;
}
mapping(uint256 => InstanceStorage) internal instances;
mapping(bytes32 => bool) public hasInstance;
mapping(bytes32 => bool) public nullifierHashes;
mapping(bytes32 => bool) public commitments;
uint256 public instanceIndex;
error Initialized();
error DuplicatedInstance();
error InvalidSignatureType();
receive() external payable {
require(msg.sender == address(weth), 'Not from WETH');
}
function initialize(
IWETH _weth,
IVerifier _verifier,
IHasher _hasher,
uint32 _levels,
address _feeTo,
CreateInstance[] memory _instances
) external virtual {
if (address(hasher) != address(0)) {
revert Initialized();
}
require(_levels > 0, "_levels should be greater than zero");
require(_levels < 32, "_levels should be less than 32");
weth = _weth;
levels = _levels;
hasher = _hasher;
verifier = _verifier;
feeTo = _feeTo;
for (uint256 i; i < _instances.length; ++i) {
CreateInstance memory _instance = _instances[i];
createInstance(_instance.token, _instance.denomination, _instance.vault);
}
}
struct CreateInstance {
IERC20 token;
uint256 denomination;
IVault vault;
}
function createInstance(IERC20 _token, uint256 _denomination, IVault _vault) public virtual nonReentrant returns (uint256 id) {
id = instanceIndex;
bytes32 instanceHash = keccak256(abi.encode(address(_token), _denomination, address(_vault)));
if (hasInstance[instanceHash]) {
revert DuplicatedInstance();
}
hasInstance[instanceHash] = true;
instances[id].token = _token;
instances[id].denomination = _denomination;
instances[id].vault = _vault;
bytes32 currentZero = bytes32(ZERO_VALUE);
for (uint32 i = 0; i < levels; i++) {
instances[id].zeros[i] = currentZero;
instances[id].filledSubtrees[i] = currentZero;
currentZero = hashLeftRight(currentZero, currentZero);
}
instances[id].roots[0] = currentZero;
instanceIndex++;
}
function witness(PermitCommitments memory permitData) public pure returns (bytes32) {
return keccak256(
abi.encode(COMMITMENT_TYPEHASH, permitData.instancesHash, permitData.commitmentsHash)
);
}
function getSignatureType(bytes memory permitData) public pure returns (SignatureType) {
(bytes1 sigType) = abi.decode(permitData, (bytes1));
if (uint8(sigType) == uint8(0)) {
return SignatureType.PERMIT;
} else if (uint8(sigType) == uint8(1)) {
return SignatureType.PERMIT2;
} else {
revert InvalidSignatureType();
}
}
function _parseDeposit(
uint256[] memory _ids,
bytes32[] memory _commitments
) internal view returns (IERC20 _token, uint256 _amount, IVault _vault) {
require(_ids.length == _commitments.length, "Incorrect input");
_token = instances[_ids[0]].token;
_vault = instances[_ids[0]].vault;
for (uint i; i < _ids.length; ++i) {
require(_token == instances[_ids[i]].token, "Incorrect Token");
require(_vault == instances[_ids[i]].vault, "Incorrect Vault");
_amount += instances[_ids[i]].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 _commitments the note commitment, which is PedersenHash(nullifier + secret)
* @dev Does not support fee-on-transfer tokens
*/
function deposit(
uint256[] memory _ids,
bytes32[] memory _commitments,
bytes memory permitData
) external virtual payable nonReentrant {
(IERC20 _token, uint256 _amount, IVault _vault) = _parseDeposit(_ids, _commitments);
bool native = address(_token) == address(weth);
require(msg.value == 0 || (native && msg.value == _amount), "Please send correct ETH");
// Let Vault handle deposits
if (address(_vault) != address(0)) {
_vault.deposit{ value: msg.value }(
_ids,
address(_token),
msg.sender,
_amount,
permitData
);
// Handle native ETH deposits
} else if (native && msg.value == _amount) {
weth.deposit{ value: _amount }();
// Handle approved token deposits
} else if (permitData.length == 0) {
_token.safeTransferFrom(msg.sender, address(this), _amount);
// Handle signature deposits (PERMIT, PERMIT2)
} else {
SignatureType sigType = getSignatureType(permitData);
if (sigType == SignatureType.PERMIT) {
(, address owner, bytes memory signature) = abi.decode(permitData, (bytes1, address, bytes));
(uint8 v, bytes32 r, bytes32 s) = ParseSignature.parse(signature);
bytes32 commitmentsHash = keccak256(abi.encodePacked(_commitments));
_token.permit(owner, address(this), _amount, uint256(commitmentsHash), v, r, s);
_token.safeTransferFrom(owner, address(this), _amount);
} else {
(, address owner, uint256 nonce, uint256 deadline, bytes memory signature) = abi.decode(
permitData, (bytes1, address, uint256, uint256, bytes)
);
bytes32 witnessHash;
{
witnessHash = witness(PermitCommitments({
instancesHash: keccak256(abi.encodePacked(_ids)),
commitmentsHash: keccak256(abi.encodePacked(_commitments))
}));
}
permit2.permitWitnessTransferFrom(
ISignatureTransfer.PermitTransferFrom({
permitted: ISignatureTransfer.TokenPermissions({
token: address(_token),
amount: _amount
}),
nonce: nonce,
deadline: deadline
}),
ISignatureTransfer.SignatureTransferDetails({
to: address(this),
requestedAmount: _amount
}),
owner,
witnessHash,
WITNESS_TYPE_STRING,
signature
);
}
}
for (uint256 i; i < _commitments.length; ++i) {
_processDeposit(_ids[i], _commitments[i]);
instances[_ids[i]].delta++;
}
}
function _processDeposit(uint256 id, bytes32 _commitment) internal {
require(!commitments[_commitment], "The commitment has been submitted");
uint32 insertedIndex = _insert(id, _commitment);
commitments[_commitment] = true;
emit Deposit(id, tx.origin, _commitment, insertedIndex, block.timestamp);
}
/**
* @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(
uint256 id,
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _relayerFee,
uint256 _refund,
bytes memory _data
) external virtual payable nonReentrant {
_processWithdraw(id, _proof, _root, _nullifierHash, _recipient, _relayer, _relayerFee, _refund, _data);
_processSend(id, _recipient, _relayer, _relayerFee, _refund, _data);
}
function _processSend(
uint256 id,
address payable _recipient,
address payable _relayer,
uint256 _relayerFee,
uint256 _refund,
bytes memory _data
) internal {
IERC20 _token = instances[id].token;
bool native = address(_token) == address(weth);
uint256 _denomination = instances[id].denomination;
if (address(instances[id].vault) != address(0)) {
instances[id].vault.withdraw{ value: msg.value }(
id,
address(_token),
_recipient,
_denomination,
_relayer,
_relayerFee,
_refund,
_data
);
instances[id].delta--;
return;
}
if (native) {
weth.withdraw(_denomination);
}
uint256 toSend;
{
(uint256 rewards, uint256 fees) = _processFees(id);
toSend = _denomination + rewards - fees - _relayerFee;
}
instances[id].delta--;
if (native) {
// 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: toSend }(_data);
require(success, "payment to _recipient did not go thru");
if (_relayerFee > 0) {
(success, ) = _relayer.call{ value: _relayerFee }("");
require(success, "payment to _relayer did not go thru");
}
} else {
require(msg.value == _refund, "Incorrect refund amount received by the contract");
_token.safeTransfer(_recipient, toSend);
if (_relayerFee > 0) {
_token.safeTransfer(_relayer, _relayerFee);
}
if (_refund > 0 || _data.length != 0) {
(bool success, ) = _recipient.call{ value: _refund }(_data);
require(success, "refund to _recipient did not go thru");
}
}
}
function _processFees(uint256 id) internal returns (uint256, uint256) {
IERC20 _token = instances[id].token;
bool native = address(_token) == address(weth);
uint256 _denomination = instances[id].denomination;
// reward to pay to
uint256 rewards = instances[id].rewards / (instances[id].delta * _denomination);
instances[id].rewards -= rewards;
// withdrawal fees to collect (doesn't collect when this is the last withdrawal)
uint256 fees = (instances[id].delta > 1) ? (_denomination * feeRate / 10000) : 0;
uint256 devFee = (feeTo != address(0)) ? (fees / 2) : 0;
uint256 dividend = fees - devFee;
if (devFee != 0) {
if (native) {
(bool success, ) = feeTo.call{ value: devFee }("");
require(success, "payment to feeTo did not go thru");
} else {
_token.safeTransfer(feeTo, devFee);
}
}
instances[id].rewards += dividend;
return (rewards, fees);
}
function _processWithdraw(
uint256 id,
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund,
bytes memory _data
) internal {
require(_fee <= instances[id].denomination, "Fee exceeds transfer value");
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
require(isKnownRoot(id, _root), "Cannot find your merkle root"); // Make sure to use a recent one
uint256 recipientInt = uint256(uint160(address(_recipient)));
if (_data.length != 0) {
// Make sure the hashed value is under PRIME_Q value
recipientInt = uint256(uint248(bytes31(keccak256(abi.encode(_recipient, _data)))));
}
require(
verifier.verifyProof(
_proof,
[uint256(_root), uint256(_nullifierHash), recipientInt, uint256(uint160(address(_relayer))), _fee, _refund]
),
"Invalid withdraw proof"
);
nullifierHashes[_nullifierHash] = true;
emit Withdrawal(id, _recipient, _nullifierHash, _relayer, _fee, block.timestamp);
}
/**
* @dev Hash 2 tree leaves, returns MiMC(_left, _right)
*/
function hashLeftRight(
bytes32 _left,
bytes32 _right
) public view 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(uint256 id, bytes32 _leaf) internal returns (uint32 index) {
uint32 _nextIndex = instances[id].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 = instances[id].zeros[i];
instances[id].filledSubtrees[i] = currentLevelHash;
} else {
left = instances[id].filledSubtrees[i];
right = currentLevelHash;
}
currentLevelHash = hashLeftRight(left, right);
currentIndex /= 2;
}
uint32 newRootIndex = (instances[id].currentRootIndex + 1) % ROOT_HISTORY_SIZE;
instances[id].currentRootIndex = newRootIndex;
instances[id].roots[newRootIndex] = currentLevelHash;
instances[id].nextIndex = _nextIndex + 1;
return _nextIndex;
}
/**
* @dev Whether the root is present in the root history
*/
function isKnownRoot(uint256 id, bytes32 _root) public view returns (bool) {
if (_root == 0) {
return false;
}
uint32 _currentRootIndex = instances[id].currentRootIndex;
uint32 i = _currentRootIndex;
do {
if (_root == instances[id].roots[i]) {
return true;
}
if (i == 0) {
i = ROOT_HISTORY_SIZE;
}
i--;
} while (i != _currentRootIndex);
return false;
}
/**
* @dev Returns the last root
*/
function nextIndex(uint256 id) public view returns (uint32) {
return instances[id].nextIndex;
}
function getLastRoot(uint256 id) public view returns (bytes32) {
return instances[id].roots[instances[id].currentRootIndex];
}
function token(uint256 id) external view returns (address) {
return address(instances[id].token);
}
function denomination(uint256 id) external view returns (uint256) {
return instances[id].denomination;
}
function instance(uint256 id) external view returns (InstanceView memory) {
return InstanceView({
currentRootIndex: instances[id].currentRootIndex,
nextIndex: instances[id].nextIndex,
token: instances[id].token,
native: address(instances[id].token) == address(weth),
denomination: instances[id].denomination,
delta: instances[id].delta,
rewards: instances[id].rewards,
vault: instances[id].vault
});
}
function filledSubtrees(uint256 id, uint256 index) external view returns (bytes32) {
return instances[id].filledSubtrees[index];
}
function zeros(uint256 id, uint256 index) external view returns (bytes32) {
return instances[id].zeros[index];
}
function roots(uint256 id, uint256 index) external view returns (bytes32) {
return instances[id].roots[index];
}
/**
* @dev whether a note is already spent
* It is discouraged to use function on remote RPC environment
* You should always sync the whole withdrawal events and find from them to ensure privacy
*/
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;
}
}
}
}