diff --git a/.env.example b/.env.example index 79f0525d..8878ffdc 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,6 @@ RINKEBY_URL= GOERLI_URL= ROPSTEN_URL= MUMBAI_URL= +POLYGON_URL= + REPORT_GAS= diff --git a/deployments/polygon/.chainId b/deployments/polygon/.chainId new file mode 100644 index 00000000..065fd3e7 --- /dev/null +++ b/deployments/polygon/.chainId @@ -0,0 +1 @@ +137 diff --git a/deployments/polygon/AludelFactory.json b/deployments/polygon/AludelFactory.json new file mode 100644 index 00000000..caef2e90 --- /dev/null +++ b/deployments/polygon/AludelFactory.json @@ -0,0 +1,515 @@ +{ + "address": "0x075d940Fa6878c6164f3F44CFc584923c4F5654C", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint16", + "name": "bps", + "type": "uint16" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AludelAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "AludelNotRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTemplate", + "type": "error" + }, + { + "inputs": [], + "name": "ProgramAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TemplateAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "TemplateDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "TemplateNotRegistered", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "program", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "ProgramAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "program", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "ProgramChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "program", + "type": "address" + } + ], + "name": "ProgramDelisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "template", + "type": "address" + } + ], + "name": "TemplateAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "disabled", + "type": "bool" + } + ], + "name": "TemplateUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "program", + "type": "address" + }, + { + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "stakingTokenUrl", + "type": "string" + }, + { + "internalType": "uint64", + "name": "startTime", + "type": "uint64" + } + ], + "name": "addProgram", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "bool", + "name": "disabled", + "type": "bool" + } + ], + "name": "addTemplate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "program", + "type": "address" + } + ], + "name": "delistProgram", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeBps", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "template", + "type": "address" + } + ], + "name": "getTemplate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "listed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "disabled", + "type": "bool" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "internalType": "struct AludelFactory.TemplateData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "isAludel", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "stakingTokenUrl", + "type": "string" + }, + { + "internalType": "uint64", + "name": "startTime", + "type": "uint64" + }, + { + "internalType": "address", + "name": "vaultFactory", + "type": "address" + }, + { + "internalType": "address[]", + "name": "bonusTokens", + "type": "address[]" + }, + { + "internalType": "address", + "name": "ownerAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "launch", + "outputs": [ + { + "internalType": "address", + "name": "aludel", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "program", + "type": "address" + } + ], + "name": "programs", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "internalType": "uint64", + "name": "startTime", + "type": "uint64" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "stakingTokenUrl", + "type": "string" + } + ], + "internalType": "struct AludelFactory.ProgramData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "bps", + "type": "uint16" + } + ], + "name": "setFeeBps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRecipient", + "type": "address" + } + ], + "name": "setFeeRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "program", + "type": "address" + }, + { + "internalType": "string", + "name": "newName", + "type": "string" + }, + { + "internalType": "string", + "name": "newUrl", + "type": "string" + } + ], + "name": "updateProgram", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "template", + "type": "address" + }, + { + "internalType": "bool", + "name": "disabled", + "type": "bool" + } + ], + "name": "updateTemplate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index aceb0933..d029051a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -84,6 +84,13 @@ const config: HardhatUserConfig = { }, saveDeployments: true, }, + polygon: { + url: process.env.POLYGON_URL || "", + accounts: { + mnemonic, + }, + saveDeployments: true, + }, avalanche: { url: "https://api.avax.network/ext/bc/C/rpc", chainId: 43114, diff --git a/tasks/aludel.ts b/tasks/aludel.ts index 22b2141c..baab46f0 100644 --- a/tasks/aludel.ts +++ b/tasks/aludel.ts @@ -3,6 +3,7 @@ import { formatEther } from "ethers/lib/utils"; import { task, types } from "hardhat/config"; import "@nomiclabs/hardhat-ethers"; import { parseEther } from "@ethersproject/units"; +import { AludelFactory } from "../typechain-types"; // this function is meant to avoid polluting the tests with console output, and // log on every other scenario @@ -226,3 +227,33 @@ task("add-program", "add a pre-existing aludel to the network's aludel factory") ).wait(); } ); + +task("update-program", "update an already added template") + .addParam("program", "address of the program to update") + .addOptionalParam("newName", "a new name for the program. Optional.", "") + .addOptionalParam("newUrl", "a new URL for the program. Optional.", "") + .setAction(async (args, { ethers, deployments }) => { + const factoryAddress = (await deployments.get("AludelFactory")).address; + const factory = (await ethers.getContractAt( + "src/contracts/AludelFactory.sol:AludelFactory", + factoryAddress + )) as AludelFactory; + + const programData = await factory.programs(args.program); + if (args.newName.length == 0 && args.newUrl.length == 0) { + throw new Error("pass --newName and/or --newUrl"); + } + + log(`updating program ${programData.name} on factory ${factoryAddress}`); + + if (args.newName) { + log(`rename ${programData.name} to ${args.newName}`); + } + if (args.newUrl) { + log(`update URL ${programData.name} to ${args.newName}`); + } + + await ( + await factory.updateProgram(args.program, args.newName, args.newUrl) + ).wait(); + }); diff --git a/test/deployments.ts b/test/deployments.ts index 2a3a4a44..bfcfc550 100644 --- a/test/deployments.ts +++ b/test/deployments.ts @@ -120,7 +120,7 @@ describe("Aludel factory deployments", function () { preexistingProgram = (await ethersFactory.deploy()) as Aludel; }); - describe("WHEN adding it with the add-program task, AND passing al parameters", () => { + describe("WHEN adding it with the add-program task, AND passing all parameters", () => { beforeEach(async () => { await run("add-program", { program: preexistingProgram.address, @@ -134,6 +134,77 @@ describe("Aludel factory deployments", function () { const program = await factory.programs(preexistingProgram.address); expect(program.name).to.eq("some name"); }); + describe("WHEN updates the program using update-program", () => { + let program: AludelFactory.ProgramDataStruct; + beforeEach(async () => { + program = await factory.programs(preexistingProgram.address); + }); + describe("AND updates the name", async () => { + beforeEach(async () => { + await run("update-program", { + program: preexistingProgram.address, + newName: "a brave new name", + }); + }); + it("THEN only the name is changed", async () => { + const updatedProgram = await factory.programs( + preexistingProgram.address + ); + expect(updatedProgram.name).to.eq("a brave new name"); + expect(updatedProgram.stakingTokenUrl).to.eq( + program.stakingTokenUrl + ); + expect(updatedProgram.template).to.eq(program.template); + expect(updatedProgram.startTime).to.eq(program.startTime); + }); + }); + describe("AND updates the url", async () => { + beforeEach(async () => { + await run("update-program", { + program: preexistingProgram.address, + newUrl: "https://new.domain", + }); + }); + it("THEN only the url is changed", async () => { + const updatedProgram = await factory.programs( + preexistingProgram.address + ); + expect(updatedProgram.name).to.eq(program.name); + expect(updatedProgram.stakingTokenUrl).to.eq( + "https://new.domain" + ); + expect(updatedProgram.template).to.eq(program.template); + expect(updatedProgram.startTime).to.eq(program.startTime); + }); + }); + describe("AND update the name and the url", async () => { + beforeEach(async () => { + await run("update-program", { + program: preexistingProgram.address, + newName: "a brave new name", + newUrl: "https://new.domain", + }); + }); + it("THEN only the name and the url are changed", async () => { + const updatedProgram = await factory.programs( + preexistingProgram.address + ); + expect(updatedProgram.name).to.eq("a brave new name"); + expect(updatedProgram.stakingTokenUrl).to.eq( + "https://new.domain" + ); + expect(updatedProgram.template).to.eq(program.template); + expect(updatedProgram.startTime).to.eq(program.startTime); + }); + }); + describe("BUT updates nothing", async () => { + it("THEN it throws", async () => { + await expect( + run("update-program", { program: preexistingProgram.address }) + ).to.be.rejectedWith("pass --newName and/or --newUrl"); + }); + }); + }); }); it("WHEN adding it with the add-program task, AND omitting the template THEN it fails because the template is not optional", async () => {