@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
285 lines (242 loc) • 14.4 kB
text/typescript
import { ethers, upgrades } from "hardhat";
import fs from 'fs';
import path from 'path';
import { getNextContractAddress } from "../../test/utils/utils";
import { getInitializerData } from "@openzeppelin/hardhat-upgrades/dist/utils";
import readline from 'readline';
import { Treasury, Directory, IRocketStorage, IConstellationOracle, OperatorDistributor, PriceFetcher, RPLVault, SuperNodeAccount, WETHVault, Whitelist, NodeSetOperatorRewardDistributor, PoAConstellationOracle, MerkleClaimStreamer } from "../../typechain-types";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { Protocol, Signers } from "../../test/test";
import { RocketStorage, RocketTokenRPL } from "../../test/rocketpool/_utils/artifacts";
import { ERC20 } from "../../typechain-types/contracts/Testing/Rocketpool/contract/util";
import { expect } from "chai";
// Function to prompt user for input
function askQuestion(query: string): Promise<string> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise<string>(resolve => rl.question(query, ans => {
rl.close();
resolve(ans);
}));
}
// Updated retry operation function
export async function retryOperation(operation: () => Promise<any>, retries: number = 3, extendedRetries: number = 3) {
try {
return await operation();
} catch (error) {
console.log(error);
if (retries > 0) {
console.log(`Retrying operation, attempts remaining: ${retries}...`);
return await retryOperation(operation, retries - 1, extendedRetries);
} else if (extendedRetries > 0) {
const answer = await askQuestion('Operation failed. Do you want to retry? (y/n): ');
if (answer.toLowerCase() === 'y') {
console.log(`Extended retry, attempts remaining: ${extendedRetries}...`);
return await retryOperation(operation, 0, extendedRetries - 1);
} else {
throw new Error('Operation aborted by the user.');
}
} else {
throw error;
}
}
}
// Function to generate bytes32 representation for contract identifiers
export const generateBytes32Identifier = (identifier: string) => {
// Correctly concatenate 'contract.address' with the identifier before hashing
return ethers.utils.solidityKeccak256(["string"], [`contract.address${identifier}`]);
};
export async function fastDeployProtocol(treasurer: SignerWithAddress, deployer: SignerWithAddress, nodesetAdmin: SignerWithAddress, nodesetServerAdmin: SignerWithAddress, directoryDeployer: SignerWithAddress, rocketStorage: string, weth: string, sanctions: string, admin: string, log: boolean, defaultOffset = 1) {
const directoryAddress = await getNextContractAddress(directoryDeployer, defaultOffset)
const whitelistProxy = await retryOperation(async () => {
const whitelist = await upgrades.deployProxy(await ethers.getContractFactory("contracts/Constellation/Whitelist.sol:Whitelist", deployer), [directoryAddress], { 'initializer': 'initializeWhitelist', 'kind': 'uups', 'unsafeAllow': ['constructor'] });
if (log) console.log("whitelist deployed to", whitelist.address)
return whitelist;
});
const vCWETHProxy = await retryOperation(async () => {
const vCWETH = await upgrades.deployProxy(await ethers.getContractFactory("WETHVault", deployer), [directoryAddress, weth], { 'initializer': 'initializeVault', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("vaulted constellation eth deployed to", vCWETH.address)
return vCWETH;
});
const oracleProxy = await retryOperation(async () => {
const oracle = await upgrades.deployProxy(await ethers.getContractFactory("PoAConstellationOracle", deployer), [directoryAddress], { 'initializer': 'initializeOracle', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("admin oracle deployed to", oracle.address)
return oracle;
});
const addressRplContract = await retryOperation(async () => {
const bytes32IdentifierRplContract = generateBytes32Identifier('rocketTokenRPL');
const rocketStorageDeployment = await ethers.getContractAt("RocketStorage", rocketStorage);
const addressRplContract = await rocketStorageDeployment.getAddress(bytes32IdentifierRplContract);
return addressRplContract
})
const rplContract = await retryOperation(async function () {
return await ethers.getContractAt("@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20", addressRplContract);
});
const vCRPLProxy = await retryOperation(async function () {
const vCRPL = await upgrades.deployProxy(await ethers.getContractFactory("RPLVault", deployer), [directoryAddress, rplContract.address], { 'initializer': 'initializeVault', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("vaulted constellation rpl deployed to", vCRPL.address)
return vCRPL
})
const operatorDistributorProxy = await retryOperation(async function () {
const od = await upgrades.deployProxy(await ethers.getContractFactory("OperatorDistributor", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("operator distributor deployed to", od.address)
return od
})
const merkleClaimStreamerProxy = await retryOperation(async function () {
const od = await upgrades.deployProxy(await ethers.getContractFactory("MerkleClaimStreamer", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("merkle claim streamer deployed to", od.address)
return od
})
const yieldDistributorProxy = await retryOperation(async function () {
const yd = await upgrades.deployProxy(await ethers.getContractFactory("NodeSetOperatorRewardDistributor", deployer), [nodesetAdmin.address, nodesetServerAdmin.address], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] });
if (log) console.log("yield distributor deployed to", yd.address)
return yd
})
const priceFetcherProxy = await retryOperation(async function () {
const pf = await upgrades.deployProxy(await ethers.getContractFactory("PriceFetcher", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] });
if (log) console.log("price fetcher deployed to", pf.address)
return pf
})
const treasuryProxy = await retryOperation(async function () {
const at = await upgrades.deployProxy(await ethers.getContractFactory("Treasury", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] });
if (log) console.log("admin treasury deployed to", at.address)
return at
})
const superNodeProxy = await retryOperation(async function () {
const snap = await upgrades.deployProxy(await ethers.getContractFactory("SuperNodeAccount", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] });
if (log) console.log("super node deployed to", snap.address)
return snap
})
if (log) {
console.log("verify directory input");
console.log("whitelistProxy.address", whitelistProxy.address)
console.log("vCWETHProxy.address", vCWETHProxy.address)
console.log("vCRPLProxy.address", vCRPLProxy.address)
console.log("operatorDistributorProxy.address", operatorDistributorProxy.address)
console.log("merkleClaimStreamerProxy.address", merkleClaimStreamerProxy.address)
console.log("yieldDistributorProxy.address", yieldDistributorProxy.address)
console.log("oracle", oracleProxy.address)
console.log("priceFetcherProxy.address", priceFetcherProxy.address)
console.log("snap.address", superNodeProxy.address)
console.log("rocketStorage", rocketStorage)
console.log("weth", weth)
console.log("sanctions", sanctions)
}
const directoryProxy = await retryOperation(async () => {
const dir = await upgrades.deployProxy(await ethers.getContractFactory("Directory", directoryDeployer),
[
[
whitelistProxy.address,
vCWETHProxy.address,
vCRPLProxy.address,
operatorDistributorProxy.address,
merkleClaimStreamerProxy.address,
yieldDistributorProxy.address,
oracleProxy.address,
priceFetcherProxy.address,
superNodeProxy.address,
rocketStorage,
weth,
sanctions,
],
treasuryProxy.address,
treasurer.address,
admin,
], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] });
if (log) console.log("directory deployed to", dir.address)
return dir
})
if (log) {
if (directoryAddress.toLocaleLowerCase() === directoryProxy.address.toLocaleLowerCase()) {
console.log("directory matches predicted address", directoryAddress)
} else {
console.error("failed to deploy directory address to predicted address", directoryAddress, directoryProxy.address)
throw new Error("Bad predicted directory")
}
}
await retryOperation(async () => {
console.log("trying to lazyInitialize superNodeProxy...")
await superNodeProxy.lazyInitialize();
})
return {
whitelist: whitelistProxy as Whitelist,
vCWETH: vCWETHProxy as WETHVault,
vCRPL: vCRPLProxy as RPLVault,
operatorDistributor: operatorDistributorProxy as OperatorDistributor,
merkleClaimStreamer: merkleClaimStreamerProxy as MerkleClaimStreamer,
yieldDistributor: yieldDistributorProxy as NodeSetOperatorRewardDistributor,
priceFetcher: priceFetcherProxy as PriceFetcher,
oracle: oracleProxy as PoAConstellationOracle,
superNode: superNodeProxy as SuperNodeAccount,
treasury: treasuryProxy as Treasury,
directory: directoryProxy as Directory
}
}
export async function deployProtocol(signers: Signers, log = false): Promise<Protocol> {
const RocketStorageDeployment = await RocketStorage.deployed();
const rockStorageContract = (await ethers.getContractAt(
"RocketStorage",
RocketStorageDeployment.address
)) as IRocketStorage;
const RplToken = await RocketTokenRPL.deployed();
const rplContract = (await ethers.getContractAt(
"@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
RplToken.address
)) as ERC20;
upgrades.silenceWarnings();
// deploy weth
const WETH = await ethers.getContractFactory("WETH");
const wETH = await WETH.deploy();
await wETH.deployed();
// deploy mock sanctions
const Sanctions = await ethers.getContractFactory("MockSanctions");
const sanctions = await Sanctions.deploy();
await sanctions.deployed();
const deployer = (await ethers.getSigners())[0];
const { whitelist, vCWETH, vCRPL, operatorDistributor, merkleClaimStreamer, superNode, oracle, yieldDistributor, priceFetcher, directory, treasury } = await fastDeployProtocol(
signers.treasurer,
signers.deployer,
signers.nodesetAdmin,
signers.nodesetServerAdmin,
signers.random5,
rockStorageContract.address,
wETH.address,
sanctions.address,
signers.admin.address,
log
)
// set adminServer to be ADMIN_SERVER_ROLE
const adminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_SERVER_ROLE"));
let tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(adminRole), signers.adminServer.address);
await tx.wait();
// set timelock to be TIMELOCK_ROLE
const timelockRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TIMELOCK_SHORT"));
tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(timelockRole), signers.admin.address);
await tx.wait();
const timelockRoleMed = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TIMELOCK_MED"));
tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(timelockRoleMed), signers.admin.address);
await tx.wait();
const timelockLongRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TIMELOCK_LONG"));
tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(timelockLongRole), signers.admin.address);
await tx.wait();
const oracleAdminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE"));
tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(oracleAdminRole), signers.admin.address);
await tx.wait();
// set protocolSigner to be PROTOCOL_ROLE
const protocolRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("CORE_PROTOCOL_ROLE"));
tx = await directory.connect(signers.admin).grantRole(ethers.utils.arrayify(protocolRole), signers.protocolSigner.address);
await tx.wait();
expect(await directory.getTreasuryAddress()).to.equal(treasury.address);
const returnData: Protocol = { treasury, directory, whitelist, vCWETH, vCRPL, operatorDistributor, merkleClaimStreamer, superNode, yieldDistributor, oracle, priceFetcher, wETH, sanctions };
// send all rpl from admin to rplWhale
const rplWhaleBalance = await rplContract.balanceOf(signers.deployer.address);
tx = await rplContract.transfer(signers.rplWhale.address, rplWhaleBalance);
await tx.wait();
let hasProtocolRole = await returnData.directory.hasRole(protocolRole, signers.protocolSigner.address);
while(!(hasProtocolRole)) {
hasProtocolRole = await returnData.directory.hasRole(protocolRole, signers.protocolSigner.address);
}
return returnData;
}