forked from oxen-io/eth-sn-contracts
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
script to deploy bulk investor contracts
- Loading branch information
Showing
5 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,7 @@ artifacts | |
|
||
# echidna | ||
crytic-export | ||
|
||
deployments | ||
|
||
investors.csv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |