initialise

This commit is contained in:
gozzy 2022-09-02 17:59:39 +00:00
commit ca9c848f6a
17 changed files with 1026 additions and 0 deletions

22
LICENSE Normal file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 Truffle
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.

43
README.md Normal file

@ -0,0 +1,43 @@
# Tornado.cash token (TORN) [![Build Status](https://github.com/tornadocash/torn-token/workflows/build/badge.svg)](https://github.com/tornadocash/torn-token/actions)
## Dependencies
1. node 12
2. yarn
## Start
```bash
$ yarn
$ cp .env.example .env
$ yarn test
```
## Deploying
Deploy to Kovan:
```bash
$ yarn deploy:kovan
```
## Mainnet deploy instructions
1. in torn-token repo `cp .env.example .env`
2. Specify deployment `PRIVATE_KEY` in the `.env`. It will be the owner during deployment.
3. Specify gas price in `truffle.js`
4. `yarn deploy:mainnet`
5. go to [mining](https://github.com/tornadocash/tornado-anonymity-mining) repo
6. `cp .env.example .env`
7. Specify private key and TORN address
8. yarn `yarn deploy:mainnet`
9. go to [governance](https://github.com/tornadocash/governance) repo
10. ...
11. ...
12. ...
13. in this repo modify the `config.js` file. Put all addresses that you got during deployment
14. set `ONLY_INITIALIZE` to `true` and `TORN_TO_INITIALIZE` to the token address
15. `yarn deploy:mainnet`

226
config.js Normal file

@ -0,0 +1,226 @@
const { toWei } = require('web3-utils')
module.exports = {
torn: {
address: 'torn.contract.tornadocash.eth',
cap: toWei('10000000'),
pausePeriod: 45 * 24 * 3600, // 45 days
distribution: {
airdrop: { to: 'voucher', amount: toWei('500000') },
miningV2: { to: 'rewardSwap', amount: toWei('1000000') },
governance: { to: 'vesting.governance', amount: toWei('5500000') },
team1: { to: 'vesting.team1', amount: toWei('822407') },
team2: { to: 'vesting.team2', amount: toWei('822407') },
team3: { to: 'vesting.team3', amount: toWei('822407') },
team4: { to: 'vesting.team4', amount: toWei('500000') },
team5: { to: 'vesting.team5', amount: toWei('32779') },
},
},
governance: { address: 'governance.contract.tornadocash.eth' },
governanceImpl: { address: 'governance-impl.contract.tornadocash.eth' },
voucher: { address: 'voucher.contract.tornadocash.eth', duration: 12 },
miningV2: {
address: 'mining-v2.contract.tornadocash.eth',
initialBalance: toWei('25000'),
rates: [
{ instance: 'eth-01.tornadocash.eth', value: '10' },
{ instance: 'eth-1.tornadocash.eth', value: '20' },
{ instance: 'eth-10.tornadocash.eth', value: '50' },
{ instance: 'eth-100.tornadocash.eth', value: '400' },
],
},
rewardSwap: { address: 'reward-swap.contract.tornadocash.eth', poolWeight: 1e11 },
tornadoTrees: { address: 'tornado-trees.contract.tornadocash.eth', levels: 20 },
tornadoProxy: { address: 'tornado-proxy.contract.tornadocash.eth' },
rewardVerifier: { address: 'reward-verifier.contract.tornadocash.eth' },
treeUpdateVerifier: { address: 'tree-update-verifier.contract.tornadocash.eth' },
withdrawVerifier: { address: 'withdraw-verifier.contract.tornadocash.eth' },
poseidonHasher2: { address: 'poseidon2.contract.tornadocash.eth' },
poseidonHasher3: { address: 'poseidon3.contract.tornadocash.eth' },
deployer: { address: 'deployer.contract.tornadocash.eth' },
vesting: {
team1: {
address: 'team1.vesting.contract.tornadocash.eth',
beneficiary: '0x5A7a51bFb49F190e5A6060a5bc6052Ac14a3b59f',
cliff: 12,
duration: 36,
},
team2: {
address: 'team2.vesting.contract.tornadocash.eth',
beneficiary: '0xF50D442e48E11F16e105431a2664141f44F9feD8',
cliff: 12,
duration: 36,
},
team3: {
address: 'team3.vesting.contract.tornadocash.eth',
beneficiary: '0x6D2C515Ff6A40554869C3Da05494b8D6910D075E',
cliff: 12,
duration: 36,
},
team4: {
address: 'team4.vesting.contract.tornadocash.eth',
beneficiary: '0x504a9c37794a2341F4861bF0A44E8d4016DF8cF2',
cliff: 12,
duration: 36,
},
team5: {
address: 'team5.vesting.contract.tornadocash.eth',
beneficiary: '0x2D81713c58452c92C19b2917e1C770eEcF53Fe41',
cliff: 12,
duration: 36,
},
governance: {
address: 'governance.vesting.contract.tornadocash.eth',
cliff: 3,
duration: 60,
},
},
instances: {
netId1: {
eth: {
instanceAddress: {
0.1: '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc',
1: '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936',
10: '0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF',
100: '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291',
},
symbol: 'ETH',
decimals: 18,
},
dai: {
instanceAddress: {
100: '0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3',
1000: '0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144',
10000: '0xF60dD140cFf0706bAE9Cd734Ac3ae76AD9eBC32A',
100000: undefined,
},
tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
symbol: 'DAI',
decimals: 18,
},
cdai: {
instanceAddress: {
5000: '0x22aaA7720ddd5388A3c0A3333430953C68f1849b',
50000: '0xBA214C1c1928a32Bffe790263E38B4Af9bFCD659',
500000: '0xb1C8094B234DcE6e03f10a5b673c1d8C69739A00',
5000000: undefined,
},
tokenAddress: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643',
symbol: 'cDAI',
decimals: 8,
},
usdc: {
instanceAddress: {
100: '0xd96f2B1c14Db8458374d9Aca76E26c3D18364307',
1000: '0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D',
10000: '0xD691F27f38B395864Ea86CfC7253969B409c362d',
100000: undefined,
},
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
symbol: 'USDC',
decimals: 6,
},
cusdc: {
instanceAddress: {
5000: '0xaEaaC358560e11f52454D997AAFF2c5731B6f8a6',
50000: '0x1356c899D8C9467C7f71C195612F8A395aBf2f0a',
500000: '0xA60C772958a3eD56c1F15dD055bA37AC8e523a0D',
5000000: undefined,
},
tokenAddress: '0x39AA39c021dfbaE8faC545936693aC917d5E7563',
symbol: 'cUSDC',
decimals: 8,
},
usdt: {
instanceAddress: {
100: '0x169AD27A470D064DEDE56a2D3ff727986b15D52B',
1000: '0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f',
10000: '0xF67721A2D8F736E75a49FdD7FAd2e31D8676542a',
100000: '0x9AD122c22B14202B4490eDAf288FDb3C7cb3ff5E',
},
tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
symbol: 'USDT',
decimals: 6,
},
},
netId42: {
eth: {
instanceAddress: {
0.1: '0x8b3f5393bA08c24cc7ff5A66a832562aAB7bC95f',
1: '0xD6a6AC46d02253c938B96D12BE439F570227aE8E',
10: '0xe1BE96331391E519471100c3c1528B66B8F4e5a7',
100: '0xd037E0Ac98Dab2fCb7E296c69C6e52767Ae5414D',
},
symbol: 'ETH',
decimals: 18,
},
dai: {
instanceAddress: {
100: '0xdf2d3cC5F361CF95b3f62c4bB66deFe3FDE47e3D',
1000: '0xD96291dFa35d180a71964D0894a1Ae54247C4ccD',
10000: '0xb192794f72EA45e33C3DF6fe212B9c18f6F45AE3',
100000: undefined,
},
tokenAddress: '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa',
symbol: 'DAI',
decimals: 18,
},
cdai: {
instanceAddress: {
5000: '0x6Fc9386ABAf83147b3a89C36D422c625F44121C8',
50000: '0x7182EA067e0f050997444FCb065985Fd677C16b6',
500000: '0xC22ceFd90fbd1FdEeE554AE6Cc671179BC3b10Ae',
5000000: undefined,
},
tokenAddress: '0xe7bc397DBd069fC7d0109C0636d06888bb50668c',
symbol: 'cDAI',
decimals: 8,
},
usdc: {
instanceAddress: {
100: '0x137E2B6d185018e7f09f6cf175a970e7fC73826C',
1000: '0xcC7f1633A5068E86E3830e692e3e3f8f520525Af',
10000: '0x28C8f149a0ab8A9bdB006B8F984fFFCCE52ef5EF',
100000: undefined,
},
tokenAddress: '0x75B0622Cec14130172EaE9Cf166B92E5C112FaFF',
symbol: 'USDC',
decimals: 6,
},
cusdc: {
instanceAddress: {
5000: '0xc0648F28ABA385c8a1421Bbf1B59e3c474F89cB0',
50000: '0x0C53853379c6b1A7B74E0A324AcbDD5Eabd4981D',
500000: '0xf84016A0E03917cBe700D318EB1b7a53e6e3dEe1',
5000000: undefined,
},
tokenAddress: '0xcfC9bB230F00bFFDB560fCe2428b4E05F3442E35',
symbol: 'cUSDC',
decimals: 8,
},
usdt: {
instanceAddress: {
100: '0x327853Da7916a6A0935563FB1919A48843036b42',
1000: '0x531AA4DF5858EA1d0031Dad16e3274609DE5AcC0',
10000: '0x0958275F0362cf6f07D21373aEE0cf37dFe415dD',
100000: '0x14aEd24B67EaF3FF28503eB92aeb217C47514364',
},
tokenAddress: '0x03c5F29e9296006876d8DF210BCFfD7EA5Db1Cf1',
symbol: 'USDT',
decimals: 6,
},
},
netId5: {
eth: {
instanceAddress: {
0.1: '0x6Bf694a291DF3FeC1f7e69701E3ab6c592435Ae7',
1: '0x3aac1cC67c2ec5Db4eA850957b967Ba153aD6279',
10: '0x723B78e67497E85279CB204544566F4dC5d2acA0',
100: '0x0E3A09dDA6B20aFbB34aC7cD4A6881493f3E7bf7',
},
symbol: 'ETH',
decimals: 18,
},
},
},
}

23
contracts/Airdrop.sol Normal file

@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./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));
}
}

93
contracts/ECDSA.sol Normal 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));
}
}

35
contracts/ENS.sol Normal 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;
}
}

106
contracts/ERC20Permit.sol Normal 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 "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./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;
}
}

110
contracts/TORN.sol Normal file

@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "./ERC20Permit.sol";
import "./ENS.sol";
contract TORN is ERC20("TornadoCash", "TORN"), ERC20Burnable, ERC20Permit, Pausable, EnsResolve {
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 {
bytes32 to;
uint256 amount;
}
constructor(
bytes32 _governance,
uint256 _pausePeriod,
Recipient[] memory _vestings
) public {
address _resolvedGovernance = resolve(_governance);
governance = _resolvedGovernance;
allowedTransferee[_resolvedGovernance] = true;
for (uint256 i = 0; i < _vestings.length; i++) {
address to = resolve(_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);
}
}
}

105
contracts/Vesting.sol Normal file

@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./ENS.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 is EnsResolve {
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(
bytes32 _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(resolve(_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;
}
}

66
contracts/Voucher.sol Normal 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 "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "./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;
}
}

@ -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)));
}
}

@ -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;
}
}

@ -0,0 +1,32 @@
// 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(
bytes32 _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();
}
}

@ -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;
}
}

@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../Vesting.sol";
import "./Timestamp.sol";
contract VestingMock is Vesting, Timestamp {
constructor(
bytes32 _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();
}
}

@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/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();
}
}

56
package.json Normal file

@ -0,0 +1,56 @@
{
"name": "torn-token",
"version": "1.0.0",
"main": "config.js",
"repository": "https://github.com/tornadocash/torn-token.git",
"author": "Tornadocash team <hello@tornado.cash>",
"license": "MIT",
"files": [
"config.js",
"contracts/*"
],
"scripts": {
"compile": "truffle compile",
"coverage": "yarn compile && truffle run coverage",
"test": "truffle test",
"test:stacktrace": "yarn test --stacktrace",
"eslint": "eslint --ext .js --ignore-path .gitignore .",
"prettier:check": "prettier --check . --config .prettierrc",
"prettier:fix": "prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"deploy:mainnet": "truffle migrate --network mainnet",
"deploy:kovan": "truffle migrate --network kovan",
"deploy:dev": "truffle migrate --skip-dry-run --network test",
"init:mainnet": "truffle migrate -f 2 --to 2 --network mainnet",
"init:kovan": "truffle migrate -f 2 --to 2 --network kovan",
"init:test": "truffle migrate -f 2 --to 2 --network test",
"voucher:mainnet": "truffle migrate -f 3 --network mainnet",
"voucher:kovan": "truffle migrate -f 3 --network kovan",
"voucher:dev": "truffle migrate -f 3 --skip-dry-run --network test",
"verify": "truffle run verify --network $NETWORK"
},
"devDependencies": {
"@ticket721/e712": "^0.4.1",
"babel-eslint": "^10.1.0",
"bn-chai": "^1.0.1",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^7.5.0",
"prettier": "^2.1.2",
"prettier-plugin-solidity": "^1.0.0-alpha.59",
"rlp": "^2.2.6",
"solhint-plugin-prettier": "^0.0.4",
"solidity-coverage": "^0.7.7",
"truffle": "^5.1.29",
"truffle-flattener": "^1.4.4",
"truffle-hdwallet-provider": "^1.0.17",
"truffle-plugin-verify": "^0.3.11"
},
"dependencies": {
"@openzeppelin/contracts": "^3.1.0",
"dotenv": "^8.2.0",
"eth-sig-util": "^2.5.3",
"ethereumjs-util": "^7.0.3",
"web3": "^1.2.11"
}
}