Skip to content

Commit 3e6332c

Browse files
authored
Create CycloneV2dot2.sol
1 parent ab01279 commit 3e6332c

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

contracts/CycloneV2dot2.sol

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
pragma solidity <0.6 >=0.4.24;
2+
3+
import "./math/SafeMath.sol";
4+
import "./token/IMintableToken.sol";
5+
import "./utils/Address.sol";
6+
import "./zksnarklib/MerkleTreeWithHistory.sol";
7+
import "./zksnarklib/IVerifier.sol";
8+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
9+
10+
contract CycloneV2dot2 is MerkleTreeWithHistory, ReentrancyGuard {
11+
12+
using SafeMath for uint256;
13+
uint256 public tokenDenomination; // (10K or 100k or 1M) * 10^18
14+
uint256 public coinDenomination;
15+
uint256 public initCYCDenomination;
16+
mapping(bytes32 => bool) public nullifierHashes;
17+
mapping(bytes32 => bool) public commitments; // we store all commitments just to prevent accidental deposits with the same commitment
18+
IVerifier public verifier;
19+
IERC20 public token;
20+
IMintableToken public cycToken;
21+
address public treasury;
22+
address public govDAO;
23+
uint256 public numOfShares;
24+
uint256 public lastRewardBlock;
25+
uint256 public rewardPerBlock;
26+
uint256 public accumulateCYC;
27+
uint256 public anonymityFee;
28+
29+
modifier onlyGovDAO {
30+
// Start with an governance DAO address and will transfer to a governance DAO, e.g., Timelock + GovernorAlpha, after launch
31+
require(msg.sender == govDAO, "Only Governance DAO can call this function.");
32+
_;
33+
}
34+
35+
event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp, uint256 cycDenomination, uint256 anonymityFee);
36+
event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 reward, uint256 relayerFee);
37+
event RewardPerBlockUpdated(uint256 oldValue, uint256 newValue);
38+
event AnonymityFeeUpdated(uint256 oldValue, uint256 newValue);
39+
40+
/**
41+
@dev The constructor
42+
@param _verifier the address of SNARK verifier for this contract
43+
@param _merkleTreeHeight the height of deposits' Merkle Tree
44+
@param _govDAO governance DAO address
45+
*/
46+
constructor(
47+
address _govDAO,
48+
IERC20 _token,
49+
IMintableToken _cycToken,
50+
address _treasury,
51+
uint256 _initCYCDenomination,
52+
uint256 _coinDenomination,
53+
uint256 _tokenDenomination,
54+
uint256 _startBlock,
55+
IVerifier _verifier,
56+
uint32 _merkleTreeHeight
57+
) MerkleTreeWithHistory(_merkleTreeHeight) public {
58+
require(address(_token) != address(_cycToken), "token cannot be identical to CYC token");
59+
verifier = _verifier;
60+
treasury = _treasury;
61+
cycToken = _cycToken;
62+
token = _token;
63+
govDAO = _govDAO;
64+
if (_startBlock < block.number) {
65+
lastRewardBlock = block.number;
66+
} else {
67+
lastRewardBlock = _startBlock;
68+
}
69+
initCYCDenomination = _initCYCDenomination;
70+
coinDenomination = _coinDenomination;
71+
tokenDenomination = _tokenDenomination;
72+
numOfShares = 0;
73+
}
74+
75+
function calcAccumulateCYC() internal view returns (uint256) {
76+
uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock);
77+
uint256 remaining = cycToken.balanceOf(address(this)).sub(accumulateCYC);
78+
if (remaining < reward) {
79+
reward = remaining;
80+
}
81+
return accumulateCYC.add(reward);
82+
}
83+
84+
function updateBlockReward() public {
85+
uint256 blockNumber = block.number;
86+
if (blockNumber <= lastRewardBlock) {
87+
return;
88+
}
89+
if (rewardPerBlock != 0) {
90+
accumulateCYC = calcAccumulateCYC();
91+
}
92+
// always update lastRewardBlock no matter there is sufficient reward or not
93+
lastRewardBlock = blockNumber;
94+
}
95+
96+
function cycDenomination() public view returns (uint256) {
97+
if (numOfShares == 0) {
98+
return initCYCDenomination;
99+
}
100+
uint256 blockNumber = block.number;
101+
uint256 accCYC = accumulateCYC;
102+
if (blockNumber > lastRewardBlock && rewardPerBlock > 0) {
103+
accCYC = calcAccumulateCYC();
104+
}
105+
return accCYC.add(numOfShares - 1).div(numOfShares);
106+
}
107+
108+
/**
109+
@dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance.
110+
@param _commitment the note commitment, which is PedersenHash(nullifier + secret)
111+
*/
112+
function deposit(bytes32 _commitment) external payable nonReentrant {
113+
require(!commitments[_commitment], "The commitment has been submitted");
114+
require(msg.value >= coinDenomination, "insufficient coin amount");
115+
uint256 refund = msg.value - coinDenomination;
116+
uint32 insertedIndex = _insert(_commitment);
117+
commitments[_commitment] = true;
118+
updateBlockReward();
119+
uint256 cycDeno = cycDenomination();
120+
uint256 fee = anonymityFee;
121+
if (cycDeno.add(fee) > 0) {
122+
require(cycToken.transferFrom(msg.sender, address(this), cycDeno.add(fee)), "insufficient CYC allowance");
123+
}
124+
if (fee > 0) {
125+
address t = treasury;
126+
if (t == address(0)) {
127+
require(cycToken.burn(fee), "failed to burn anonymity fee");
128+
} else {
129+
require(safeTransfer(cycToken, t, fee), "failed to transfer anonymity fee");
130+
}
131+
}
132+
uint256 td = tokenDenomination;
133+
if (td > 0) {
134+
require(token.transferFrom(msg.sender, address(this), td), "insufficient allowance");
135+
}
136+
accumulateCYC += cycDeno;
137+
numOfShares += 1;
138+
if (refund > 0) {
139+
(bool success, ) = msg.sender.call.value(refund)("");
140+
require(success, "failed to refund");
141+
}
142+
emit Deposit(_commitment, insertedIndex, block.timestamp, cycDeno, fee);
143+
}
144+
145+
/**
146+
@dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
147+
`input` array consists of:
148+
- merkle root of all deposits in the contract
149+
- hash of unique deposit nullifier to prevent double spends
150+
- the recipient of funds
151+
- optional fee that goes to the transaction sender (usually a relay)
152+
*/
153+
function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _relayerFee, uint256 _refund) external payable nonReentrant {
154+
require(_refund == 0, "refund is not zero");
155+
require(!Address.isContract(_recipient), "recipient of cannot be contract");
156+
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
157+
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
158+
require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _relayerFee, _refund]), "Invalid withdraw proof");
159+
160+
nullifierHashes[_nullifierHash] = true;
161+
uint256 td = tokenDenomination;
162+
if (td > 0) {
163+
require(safeTransfer(token, _recipient, td), "failed to withdraw token");
164+
}
165+
updateBlockReward();
166+
uint256 relayerFee = 0;
167+
// numOfShares should be larger than 0
168+
uint256 cycDeno = accumulateCYC.div(numOfShares);
169+
if (cycDeno > 0) {
170+
accumulateCYC -= cycDeno;
171+
require(safeTransfer(cycToken, _recipient, cycDeno), "failed to reward CYC");
172+
}
173+
uint256 cd = coinDenomination;
174+
if (_relayerFee > cd) {
175+
_relayerFee = cd;
176+
}
177+
if (_relayerFee > 0) {
178+
(bool success,) = _relayer.call.value(_relayerFee)("");
179+
require(success, "failed to send relayer fee");
180+
cd -= _relayerFee;
181+
}
182+
if (cd > 0) {
183+
(bool success,) = _recipient.call.value(cd)("");
184+
require(success, "failed to withdraw coin");
185+
}
186+
numOfShares -= 1;
187+
emit Withdrawal(_recipient, _nullifierHash, _relayer, cycDeno, relayerFee);
188+
}
189+
190+
/** @dev whether a note is already spent */
191+
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
192+
return nullifierHashes[_nullifierHash];
193+
}
194+
195+
/** @dev whether an array of notes is already spent */
196+
function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) {
197+
spent = new bool[](_nullifierHashes.length);
198+
for(uint i = 0; i < _nullifierHashes.length; i++) {
199+
if (isSpent(_nullifierHashes[i])) {
200+
spent[i] = true;
201+
}
202+
}
203+
}
204+
205+
/**
206+
@dev allow governance DAO to update SNARK verification keys. This is needed to
207+
update keys if tornado.cash update their keys in production.
208+
*/
209+
function updateVerifier(address _newVerifier) external onlyGovDAO {
210+
verifier = IVerifier(_newVerifier);
211+
}
212+
213+
/** @dev governance DAO can change his address */
214+
function changeGovDAO(address _newGovDAO) external onlyGovDAO {
215+
govDAO = _newGovDAO;
216+
}
217+
218+
function setRewardPerBlock(uint256 _rewardPerBlock) public onlyGovDAO {
219+
updateBlockReward();
220+
emit RewardPerBlockUpdated(rewardPerBlock, _rewardPerBlock);
221+
rewardPerBlock = _rewardPerBlock;
222+
}
223+
224+
function setAnonymityFee(uint256 _fee) public onlyGovDAO {
225+
emit AnonymityFeeUpdated(anonymityFee, _fee);
226+
anonymityFee = _fee;
227+
}
228+
229+
// Safe transfer function, just in case if rounding error causes pool to not have enough CYCs.
230+
function safeTransfer(IERC20 _token, address _to, uint256 _amount) internal returns (bool) {
231+
uint256 balance = _token.balanceOf(address(this));
232+
if (_amount > balance) {
233+
return _token.transfer(_to, balance);
234+
}
235+
return _token.transfer(_to, _amount);
236+
}
237+
238+
function version() public pure returns(string memory) {
239+
return "2.2";
240+
}
241+
242+
}

0 commit comments

Comments
 (0)