Skip to content

Commit

Permalink
script to deploy bulk investor contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
darcys22 committed Jan 31, 2025
1 parent 6065f17 commit d0cf853
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ artifacts

# echidna
crytic-export

deployments

investors.csv
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@openzeppelin/contracts-upgradeable": "^5.0.2",
"@openzeppelin/hardhat-upgrades": "^3.3.0",
"chalk": "^4.1.2",
"csv-parse": "^5.6.0",
"dotenv": "^16.4.5",
"hardhat-diamond-abi": "^3.0.1",
"prettier": "^3.2.5",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

223 changes: 223 additions & 0 deletions scripts/deploy-investor-vesting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
const hre = require("hardhat");
const fs = require('fs');
const csv = require('csv-parse/sync');
const chalk = require('chalk');

// This script will deploy many investor contract, it takes as input a CSV "investors.csv" which is required to have
// these headers: beneficiary,revoker,start,end,transferableBeneficiary,amount
//
// After deploying the investor contracts it will fund them with sesh tokens from the deployers address so this will require
// the deployer having enough tokens in their account.
//
// Finally the script will finish by producing both a json and a CSV with the deployed contracts in a newly created "deployments" folder

//TODO set these contract addresses
const seshAddress = "0x7D7fD4E91834A96cD9Fb2369E7f4EB72383bbdEd";
const rewardsAddress = "0x9d8aB00880CBBdc2Dcd29C179779469A82E7be35";
const multiContributorAddress = "0x36Ee2Da54a7E727cC996A441826BBEdda6336B71";

async function verifyContract(address, constructorArgs) {
console.log(chalk.yellow("\nVerifying contract on Etherscan..."));
try {
await hre.run("verify:verify", {
address: address,
constructorArguments: constructorArgs,
contract: "contracts/utils/TokenVestingStaking.sol:TokenVestingStaking"
});
console.log(chalk.green("Contract verified successfully"));
} catch (error) {
if (error.message.includes("already been verified")) {
console.log(chalk.yellow("Contract already verified"));
} else {
console.error(chalk.red("Error verifying contract:"), error);
}
}
}

async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying contracts with account:", chalk.yellow(deployer.address));

const networkName = hre.network.name;
console.log("Network:", chalk.cyan(networkName));

let apiKey;
if (typeof hre.config.etherscan?.apiKey === "object") {
apiKey = hre.config.etherscan.apiKey[networkName];
} else {
apiKey = hre.config.etherscan?.apiKey;
}
if (!apiKey || apiKey == "") {
console.error(chalk.red("Error: API key for contract verification is missing."));
console.error("Please set it in your Hardhat configuration under 'etherscan.apiKey'.");
process.exit(1);
}

// Load CSV of the investors
const csvFilePath = 'investors.csv';
if (!fs.existsSync(csvFilePath)) {
console.error(chalk.red(`Error: CSV file not found at ${csvFilePath}`));
console.error("Please create a CSV file with the following headers:");
console.error("beneficiary,revoker,start,end,transferableBeneficiary,amount");
process.exit(1);
}

const fileContent = fs.readFileSync(csvFilePath);
const records = csv.parse(fileContent, {
columns: true,
skip_empty_lines: true
});

const TokenVestingStaking = await hre.ethers.getContractFactory("TokenVestingStaking");

const requiredAddresses = {
'SESH_TOKEN_ADDRESS': seshAddress,
'REWARDS_CONTRACT_ADDRESS': rewardsAddress,
'MULTICONTRIBUTOR_CONTRACT_ADDRESS': multiContributorAddress
};
for (const [name, address] of Object.entries(requiredAddresses)) {
if (!address) {
console.error(chalk.red(`Error: ${name} variable not set`));
process.exit(1);
}
if (!hre.ethers.isAddress(address)) {
console.error(chalk.red(`Error: ${name} is not a valid address: ${address}`));
process.exit(1);
}
}

const deployedContracts = [];

// Deploy contracts for each investor
for (const record of records) {
try {
console.log(chalk.cyan("\nDeploying vesting contract for:"), chalk.yellow(record.beneficiary));

if (!hre.ethers.isAddress(record.beneficiary)) {
throw new Error(`Invalid beneficiary address: ${record.beneficiary}`);
}
if (!hre.ethers.isAddress(record.revoker)) {
throw new Error(`Invalid revoker address: ${record.revoker}`);
}

const start = Math.floor(new Date(record.start).getTime() / 1000);
const end = Math.floor(new Date(record.end).getTime() / 1000);
const transferableBeneficiary = record.transferableBeneficiary.toLowerCase() === 'true';

const currentTime = Math.floor(Date.now() / 1000);
if (start <= currentTime) {
throw new Error(`Start time must be in the future. Current: ${currentTime}, Start: ${start}`);
}
if (end <= start) {
throw new Error(`End time must be after start time. Start: ${start}, End: ${end}`);
}

const constructorArgs = [
record.beneficiary,
record.revoker,
start,
end,
transferableBeneficiary,
rewardsAddress,
multiContributorAddress,
seshAddress
];

const vestingContract = await TokenVestingStaking.deploy(...constructorArgs);
await vestingContract.waitForDeployment();
const vestingAddress = await vestingContract.getAddress();

console.log(chalk.green("Vesting contract deployed to:"), chalk.yellow(vestingAddress));

console.log("Waiting for deployment to be confirmed...");
await vestingContract.deploymentTransaction().wait(5);

await verifyContract(vestingAddress, constructorArgs);

const seshContract = await hre.ethers.getContractAt("SESH", seshAddress);
const amount = hre.ethers.parseUnits(record.amount, 9); // Assuming 9 decimals for SESH
const transferTx = await seshContract.transfer(vestingAddress, amount);
await transferTx.wait();

console.log(chalk.green("Tokens transferred:"), chalk.yellow(record.amount), "SESH");

deployedContracts.push({
beneficiary: record.beneficiary,
vestingAddress: vestingAddress,
amount: record.amount,
start: new Date(start * 1000).toISOString(),
end: new Date(end * 1000).toISOString(),
transferableBeneficiary: transferableBeneficiary
});

} catch (error) {
console.error(chalk.red(`Error deploying contract for ${record.beneficiary}:`), error);
}
}

// Save deployment results as JSON
const deploymentResults = {
timestamp: new Date().toISOString(),
network: networkName,
seshAddress,
rewardsAddress,
multiContributorAddress,
contracts: deployedContracts
};

const outputDir = './deployments';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}

const jsonOutputPath = `${outputDir}/vesting-${networkName}-${Date.now()}.json`;
fs.writeFileSync(jsonOutputPath, JSON.stringify(deploymentResults, null, 2));
console.log(chalk.green("\nDeployment results saved to:"), jsonOutputPath);

// Generate and save CSV of the deployed contracts
const csvOutputPath = `${outputDir}/vesting-${networkName}-${Date.now()}.csv`;
const csvHeaders = [
'beneficiary',
'vestingAddress',
'amount',
'start',
'end',
'transferableBeneficiary'
];

const csvRows = [
csvHeaders.join(','),
...deployedContracts.map(contract => {
return [
contract.beneficiary,
contract.vestingAddress,
contract.amount,
contract.start,
contract.end,
contract.transferableBeneficiary
].join(',');
})
];

fs.writeFileSync(csvOutputPath, csvRows.join('\n'));
console.log(chalk.green("Deployment CSV summary saved to:"), csvOutputPath);

// Print summary to console
console.log(chalk.cyan("\nDeployment Summary:"));
console.log("Network:", chalk.yellow(networkName));
console.log("Total contracts deployed:", chalk.yellow(deployedContracts.length));
console.table(
deployedContracts.map(c => ({
Beneficiary: c.beneficiary,
'Vesting Contract': c.vestingAddress,
Amount: c.amount
}))
);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
6 changes: 6 additions & 0 deletions scripts/investors-example.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
beneficiary,revoker,start,end,transferableBeneficiary,amount
0x70997970C51812dc3A010C7d01b50e0d17dc79C8,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,2025-04-01T00:00:00Z,2026-04-01T00:00:00Z,true,100000
0x90F79bf6EB2c4f870365E785982E1f101E93b906,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,2025-04-01T00:00:00Z,2026-04-01T00:00:00Z,false,250000
0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,2025-04-01T00:00:00Z,2026-04-01T00:00:00Z,true,175000
0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,2025-04-01T00:00:00Z,2026-04-01T00:00:00Z,true,300000
0x976EA74026E726554dB657fA54763abd0C3a0aa9,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,2025-04-01T00:00:00Z,2026-04-01T00:00:00Z,false,125000

0 comments on commit d0cf853

Please sign in to comment.