all files / contracts/ RenPool.sol

88.24% Statements 60/68
65% Branches 26/40
83.33% Functions 15/18
87.67% Lines 64/73
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372                                                                                                                                                  18×       17×                                           15× 15×                             24×   24× 23× 22×   19× 19×   19×   19× 15×     19×                                                                                                                                                                                                                                                                                                                                                                                              
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
 
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/IDarknodeRegistry.sol";
import "../interfaces/IDarknodePayment.sol";
import "../interfaces/IClaimRewards.sol";
import "../interfaces/IGateway.sol";
// TODO: use safeMath
// TODO: Ownable + Ownable.initialize(_owner);
 
contract RenPool {
	uint8 public constant DECIMALS = 18;
 
	address public owner; // This will be our address, in case we need to refund everyone
	address public nodeOperator;
	address public darknodeID;
 
	bytes public publicKey;
	// ^ What happens if we register and deregister and register back again?
 
	uint256 public bond;
	uint256 public totalPooled;
	uint256 public ownerFee; // Percentage
	uint256 public nodeOperatorFee; // Percentage
 
	uint64 public nonce;
 
	bool public isLocked;
  // ^ we could use enum instead POOL_STATUS = { OPEN /* 0 */, CLOSE /* 1 */ }
 
	mapping(address => uint256) public balances;
	mapping(address => uint256) public withdrawRequests;
 
	IERC20 public renToken;
	IDarknodeRegistry public darknodeRegistry;
	IDarknodePayment public darknodePayment;
	IClaimRewards public claimRewards;
	IGateway public gateway; // OR IMintGateway????
 
	event RenDeposited(address indexed _from, uint256 _amount);
	event RenWithdrawn(address indexed _from, uint256 _amount);
	event EthDeposited(address indexed _from, uint256 _amount);
	event EthWithdrawn(address indexed _from, uint256 _amount);
	event PoolLocked();
	event PoolUnlocked();
 
	/**
	 * @notice Deploy a new RenPool instance.
	 *
	 * @param _renTokenAddr The REN token contract address.
	 * @param _darknodeRegistryAddr The DarknodeRegistry contract address.
	 * @param _darknodePaymentAddr The DarknodePayment contract address.
	 * @param _claimRewardsAddr The ClaimRewards contract address.
	 * @param _gatewayAddr The Gateway contract address.
	 * @param _owner The protocol owner's address. Possibly a multising wallet.
	 * @param _bond The amount of REN tokens required to register a darknode.
	 */
	constructor(
		address _renTokenAddr,
		address _darknodeRegistryAddr,
		address _darknodePaymentAddr,
		address _claimRewardsAddr,
		address _gatewayAddr,
		address _owner,
		uint256 _bond
	)
	{
		owner = _owner;
		nodeOperator = msg.sender;
		renToken = IERC20(_renTokenAddr);
		darknodeRegistry = IDarknodeRegistry(_darknodeRegistryAddr);
		darknodePayment = IDarknodePayment(_darknodePaymentAddr);
		claimRewards = IClaimRewards(_claimRewardsAddr);
		gateway = IGateway(_gatewayAddr);
		bond = _bond;
		isLocked = false;
		totalPooled = 0;
		ownerFee = 5;
		nodeOperatorFee = 5;
		nonce = 0;
 
		// TODO: register pool into RenPoolStore
	}
 
	modifier onlyNodeOperator() {
		require (
			msg.sender == nodeOperator,
			"RenPool: Caller is not node operator"
		);
		_;
	}
 
	modifier onlyOwnerNodeOperator() {
		require (
			msg.sender == owner || msg.sender == nodeOperator,
			"RenPool: Caller is not owner nor node operator"
		);
		_;
	}
 
	modifier onlyOwner() {
		require (
			msg.sender == owner,
			"RenPool: Caller is not owner"
		);
		_;
	}
 
	/**
	 * @notice Lock pool so that no direct deposits/withdrawals can
	 * be performed.
	 */
	function _lockPool() private {
		isLocked = true;
		emit PoolLocked();
	}
 
	function unlockPool() external onlyOwnerNodeOperator {
		Erequire(renToken.balanceOf(address(this)) > 0, "Pool balance is zero");
		isLocked = false;
		emit PoolUnlocked();
	}
 
	/**
	 * @notice Deposit REN into the RenPool contract. Before depositing,
	 * the transfer must be approved in the REN contract. In case the
	 * predefined bond is reached, the pool is locked preventing any
	 * further deposits or withdrawals.
	 *
	 * @param _amount The amount of REN to be deposited into the pool.
	 */
	function deposit(uint256 _amount) external {
		address sender = msg.sender;
 
		require(!isLocked, "RenPool: Pool is locked");
		require(_amount > 0, "RenPool: Invalid amount");
		require(_amount + totalPooled <= bond, "RenPool: Amount surpasses bond");
 
		balances[sender] += _amount;
		totalPooled += _amount;
 
		emit RenDeposited(sender, _amount);
 
		if (totalPooled == bond) {
			_lockPool();
		}
 
		require(renToken.transferFrom(sender, address(this), _amount), "RenPool: Deposit failed");
	}
 
	/**
     * @notice Withdraw REN token to the user's wallet from the RenPool smart contract. 
     * Cannot be called if the pool is locked. 
     *
     * @param _amount The amount of REN to be withdrawn by `sender`.
	 */
	function withdraw(uint256 _amount) external {
		address sender = msg.sender;
		uint256 senderBalance = balances[sender];
 
		Erequire(senderBalance > 0 && senderBalance >= _amount, "Insufficient funds");
		Erequire(!isLocked, "Pool is locked");
 
		totalPooled -= _amount;
		balances[sender] -= _amount;
 
		Erequire(
			renToken.transfer(sender, _amount),
			"Withdraw failed"
		);
 
		emit RenWithdrawn(sender, _amount);
	}
 
	/**
     * @notice Requesting a withdraw in case the pool is locked. The amount
     * that needs to be withdrawn will be replaced by another user using the 
     * fulfillWithdrawRequest method.
	 *
	 * @param _amount The amount of REN to be withdrawn.
	 *
	 * @dev Users can have up to a single request active. In case of several
	 * calls to this method, only the last request will be preserved.
	 */
	function requestWithdraw(uint256 _amount) external {
		address sender = msg.sender;
		uint256 senderBalance = balances[sender];
 
		Erequire(senderBalance > 0 && senderBalance >= _amount, "Insufficient funds");
		Erequire(isLocked, "Pool is not locked");
 
		withdrawRequests[sender] = _amount;
 
		// TODO emit event
	}
 
	/**
     * @notice User wanting to fullill the withdraw request will pay the amount
	 * the user wanting to withdraw his money.
	 *
	 * @param _target The amount of REN to be withdrawn.
	 */
	function fulfillWithdrawRequest(address _target) external {
		address sender = msg.sender;
		uint256 amount = withdrawRequests[_target];
		// ^ This could not be defined plus make sure amount > 0
		// TODO: make sure user cannot fullfil his own request
		// TODO: add test for when _target doesn't have an associated withdrawRequest
 
		Erequire(isLocked, "Pool is not locked");
 
		balances[sender] += amount;
		balances[_target] -= amount;
		delete withdrawRequests[_target];
 
		// Transfer funds from sender to _target
		Erequire(
			renToken.transferFrom(sender, address(this), amount),
			"Deposit failed"
		);
		Erequire(
			renToken.transfer(_target, amount),
			"Refund failed"
		);
 
		// TODO emit event
	}
 
	// TODO: cancelWithdrawRequest
	// TODO: getWithdrawRequests
 
	/**
	 * @notice Return REN balance for the given address.
	 *
	 * @param _target Address to be queried.
	 */
	function balanceOf(address _target) external view returns(uint) {
		return balances[_target];
	}
 
	/**
	 * @notice Transfer bond to the darknodeRegistry contract prior to
	 * registering the darknode.
	 */
	function approveBondTransfer() external onlyNodeOperator {
		Erequire(isLocked, "Pool is not locked");
 
		Erequire(
			renToken.approve(address(darknodeRegistry), bond),
			"Bond transfer failed"
		);
	}
 
	/**
	 * @notice Register a darknode and transfer the bond to the darknodeRegistry
	 * contract. Before registering, the bond transfer must be approved in the
	 * darknodeRegistry contract (see approveTransferBond). The caller must
	 * provide a public encryption key for the darknode. The darknode will remain
	 * pending registration until the next epoch. Only after this period can the
	 * darknode be deregistered. The caller of this method will be stored as the
	 * owner of the darknode.
	 *
	 * @param _darknodeID The darknode ID that will be registered.
	 * @param _publicKey The public key of the darknode. It is stored to allow
	 * other darknodes and traders to encrypt messages to the trader.
	 */
	function registerDarknode(address _darknodeID, bytes calldata _publicKey) external onlyNodeOperator {
		Erequire(isLocked, "Pool is not locked");
 
		darknodeRegistry.register(_darknodeID, _publicKey);
 
		darknodeID = _darknodeID;
		publicKey = _publicKey;
	}
 
	/**
	 * @notice Deregister a darknode. The darknode will not be deregistered
	 * until the end of the epoch. After another epoch, the bond can be
	 * refunded by calling the refund method.
	 *
	 * @dev We don't reset darknodeID/publicKey values after deregistration in order
	 * to being able to call refund.
	 */
	function deregisterDarknode() external onlyOwnerNodeOperator {
		darknodeRegistry.deregister(darknodeID);
	}
 
	/**
	 * @notice Refund the bond of a deregistered darknode. This will make the
	 * darknode available for registration again. Anyone can call this function
	 * but the bond will always be refunded to the darknode owner.
	 *
	 * @dev No need to reset darknodeID/publicKey values after refund.
	 */
	function refundBond() external {
		darknodeRegistry.refund(darknodeID);
	}
 
	/**
	 * @notice Allow ETH deposits in case gas is necessary to pay for transactions.
	 */
	receive() external payable {
		emit EthDeposited(msg.sender, msg.value);
	}
 
	/**
	 * @notice Allow node operator to withdraw any remaining gas.
	 */
	function withdrawGas() external onlyNodeOperator {
		uint256 balance = address(this).balance;
		payable(nodeOperator).transfer(balance);
		emit EthWithdrawn(nodeOperator, balance);
	}
 
	/**
	 * @notice Transfer rewards from darknode to darknode owner prior to calling claimDarknodeRewards.
	 *
	 * @param _tokens List of tokens to transfer. (here we could have a list with all available tokens)
	 */
	function transferRewardsToDarknodeOwner(address[] calldata _tokens) external {
		darknodePayment.withdrawMultiple(address(this), _tokens);
	}
 
	/**
	 * @notice Claim darknode rewards.
	 *
	 * @param _assetSymbol The asset being claimed. e.g. "BTC" or "DOGE".
	 * @param _amount The amount of the token being minted, in its smallest
	 * denomination (e.g. satoshis for BTC).
	 * @param _recipientAddress The Ethereum address to which the assets are
	 * being withdrawn to. This same address must then call `mint` on
	 * the asset's Ren Gateway contract.
	 */
	function claimDarknodeRewards(
		string memory _assetSymbol,
		uint256 _amount, // avoid this param, read from user balance instead. What about airdrops?
		address _recipientAddress
	)
		external
		returns(uint256, uint256)
	{
		// TODO: check that sender has the amount to be claimed
		uint256 fractionInBps = 10_000; // TODO: this should be the share of the user for the given token
		uint256 sig = claimRewards.claimRewardsToEthereum(_assetSymbol, _recipientAddress, fractionInBps);
		nonce += 1;
 
		return (sig, nonce);
		// bytes32 pHash = keccak256(abi.encode(_assetSymbol, _recipientAddress));
		// bytes32 nHash = keccak256(abi.encode(nonce, _amount, pHash));
 
		// gateway.mint(pHash, _amount, nHash, sig);
 
		/*
                    const nHash = randomBytes(32);
                    const pHash = randomBytes(32);
 
                    const hash = await gateway.hashForSignature.call(
                        pHash,
                        value,
                        user,
                        nHash
                    );
                    const sig = ecsign(
                        Buffer.from(hash.slice(2), "hex"),
                        privKey
                    );
										See: https://github.com/renproject/gateway-sol/blob/7bd51d8a897952a31134875d7b2b621e4542deaa/test/Gateway.ts
		*/
	}
}