@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
250 lines (201 loc) • 10.7 kB
JavaScript
import { printTitle } from '../_utils/formatting';
import { shouldRevert } from '../_utils/testing';
import { getDepositExcessBalance, userDeposit } from '../_helpers/deposit';
import { getMinipoolMinimumRPLStake, createMinipool, stakeMinipool } from '../_helpers/minipool';
import { submitBalances } from '../_helpers/network';
import { registerNode, setNodeTrusted, nodeStakeRPL, setNodeWithdrawalAddress } from '../_helpers/node';
import { depositExcessCollateral, getRethBalance, getRethCollateralRate, getRethExchangeRate, getRethTotalSupply, mintRPL } from '../_helpers/tokens'
import { burnReth } from './scenario-reth-burn';
import { transferReth } from './scenario-reth-transfer'
import {
RocketDAONodeTrustedSettingsMinipool, RocketDAOProtocolSettingsMinipool,
RocketDAOProtocolSettingsNetwork,
RocketTokenRETH,
} from '../_utils/artifacts';
import { setDAOProtocolBootstrapSetting } from '../dao/scenario-dao-protocol-bootstrap';
import { beginUserDistribute, withdrawValidatorBalance } from '../minipool/scenario-withdraw-validator-balance';
import { increaseTime, mineBlocks } from '../_utils/evm'
import { setDAONodeTrustedBootstrapSetting } from '../dao/scenario-dao-node-trusted-bootstrap';
import { assertBN } from '../_helpers/bn';
export default function() {
contract('RocketTokenRETH', async (accounts) => {
// Accounts
const [
owner,
node,
nodeWithdrawalAddress,
trustedNode,
staker1,
staker2,
random,
] = accounts;
let scrubPeriod = (60 * 60 * 24); // 24 hours
// Setup
let minipool;
let withdrawalBalance = '36'.ether;
let rethBalance;
let submitPricesFrequency = 500;
let depositDeplay = 100;
const userDistributeStartTime = 60 * 60 * 24 * 90; // 90 days
before(async () => {
// Get current rETH exchange rate
let exchangeRate1 = await getRethExchangeRate();
// Make deposit
await userDeposit({from: staker1, value: '16'.ether});
// Register node & set withdrawal address
await registerNode({from: node});
await setNodeWithdrawalAddress(node, nodeWithdrawalAddress, {from: node});
// Register trusted node
await registerNode({from: trustedNode});
await setNodeTrusted(trustedNode, 'saas_1', 'node@home.com', owner);
// Set settings
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.reth.collateral.target', '1'.ether, {from: owner});
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.submit.prices.frequency', submitPricesFrequency, {from: owner});
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.reth.deposit.delay', depositDeplay, {from: owner});
await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMinipool, 'minipool.scrub.period', scrubPeriod, {from: owner});
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMinipool, 'minipool.user.distribute.window.start', userDistributeStartTime, {from: owner});
// Stake RPL to cover minipools
let rplStake = await getMinipoolMinimumRPLStake();
await mintRPL(owner, node, rplStake);
await nodeStakeRPL(rplStake, {from: node});
// Create withdrawable minipool
minipool = await createMinipool({from: node, value: '16'.ether});
await increaseTime(web3, scrubPeriod + 1);
await stakeMinipool(minipool, {from: node});
// Update network ETH total to alter rETH exchange rate
let rethSupply = await getRethTotalSupply();
let nodeFee = await minipool.getNodeFee.call()
let depositBalance = '32'.ether;
let userAmount = '16'.ether;
let rewards = web3.utils.toBN(withdrawalBalance).sub(depositBalance);
let halfRewards = rewards.divn(2);
let nodeCommissionFee = halfRewards.mul(nodeFee).div('1'.ether);
let ethBalance = userAmount.add(halfRewards.sub(nodeCommissionFee));
await submitBalances(1, ethBalance, 0, rethSupply, {from: trustedNode});
// Get & check staker rETH balance
rethBalance = await getRethBalance(staker1);
assertBN.isAbove(rethBalance, 0, 'Incorrect staker rETH balance');
// Get & check updated rETH exchange rate
let exchangeRate2 = await getRethExchangeRate();
assert.notEqual(exchangeRate1, exchangeRate2, 'rETH exchange rate has not changed');
});
it(printTitle('rETH holder', 'can transfer rETH after enough time has passed'), async () => {
// Make user deposit
const depositAmount = '20'.ether;
await userDeposit({from: staker2, value: depositAmount});
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Transfer rETH
await transferReth(random, rethBalance, {
from: staker1,
});
});
it(printTitle('rETH holder', 'can transfer rETH without waiting if received via transfer'), async () => {
// Make user deposit
const depositAmount = '20'.ether;
await userDeposit({from: staker2, value: depositAmount});
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Transfer rETH
await transferReth(random, rethBalance, {
from: staker1,
});
// Transfer rETH again
await transferReth(staker1, rethBalance, {
from: random,
});
});
it(printTitle('rETH holder', 'can burn rETH for ETH collateral'), async () => {
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Send ETH to the minipool to simulate receiving from SWC
await web3.eth.sendTransaction({
from: trustedNode,
to: minipool.address,
value: withdrawalBalance
});
// Begin user distribution process
await beginUserDistribute(minipool, {from: random});
// Wait
await increaseTime(web3, userDistributeStartTime + 1)
// Withdraw without finalising
await withdrawValidatorBalance(minipool, '0'.ether, random);
// Burn rETH
await burnReth(rethBalance, {
from: staker1,
});
});
it(printTitle('rETH holder', 'can burn rETH for excess deposit pool ETH'), async () => {
// Make user deposit
const depositAmount = '20'.ether;
await userDeposit({from: staker2, value: depositAmount});
// Check deposit pool excess balance
let excessBalance = await getDepositExcessBalance();
assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance');
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Burn rETH
await burnReth(rethBalance, {
from: staker1,
});
});
it(printTitle('rETH holder', 'cannot burn an invalid amount of rETH'), async () => {
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Send ETH to the minipool to simulate receving from SWC
await web3.eth.sendTransaction({
from: trustedNode,
to: minipool.address,
value: withdrawalBalance
});
// Begin user distribution process
await beginUserDistribute(minipool, {from: random});
// Wait
await increaseTime(web3, userDistributeStartTime + 1)
// Withdraw without finalising
await withdrawValidatorBalance(minipool, '0'.ether, random);
// Get burn amounts
let burnZero = '0'.ether;
let burnExcess = '100'.ether;
assertBN.isAbove(burnExcess, rethBalance, 'Burn amount does not exceed rETH balance');
// Attempt to burn 0 rETH
await shouldRevert(burnReth(burnZero, {
from: staker1,
}), 'Burned an invalid amount of rETH');
// Attempt to burn too much rETH
await shouldRevert(burnReth(burnExcess, {
from: staker1,
}), 'Burned an amount of rETH greater than the token balance');
});
it(printTitle('rETH holder', 'cannot burn rETH with insufficient collateral'), async () => {
// Wait "network.reth.deposit.delay" blocks
await mineBlocks(web3, depositDeplay);
// Attempt to burn rETH for contract collateral
await shouldRevert(burnReth(rethBalance, {
from: staker1,
}), 'Burned rETH with an insufficient contract ETH balance');
// Make user deposit
const depositAmount = '10'.ether;
await userDeposit({from: staker2, value: depositAmount});
// Check deposit pool excess balance
let excessBalance = await getDepositExcessBalance();
assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance');
// Attempt to burn rETH for excess deposit pool ETH
await shouldRevert(burnReth(rethBalance, {
from: staker1,
}), 'Burned rETH with an insufficient deposit pool excess ETH balance');
});
it(printTitle('random', 'can deposit excess collateral into the deposit pool'), async () => {
// Get rETH contract
const rocketTokenRETH = await RocketTokenRETH.deployed();
// Send enough ETH to rETH contract to exceed target collateralisation rate
await web3.eth.sendTransaction({from: random, to: rocketTokenRETH.address, value: web3.utils.toWei('32')});
// Call the deposit excess function
await depositExcessCollateral({from: random});
// Collateral should now be at the target rate
const collateralRate = await getRethCollateralRate();
// Collateral rate should now be 1 (the target rate)
assertBN.equal(collateralRate, web3.utils.toWei('1'));
});
});
}