From bdf5364e6f09f20164f8ae062f39dbf25c60399d Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 30 Jul 2024 21:35:00 +0300 Subject: [PATCH 1/6] feat: add migration --- .../1722358576_launch_franchaisers.ts | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts new file mode 100644 index 000000000..6e81f2935 --- /dev/null +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -0,0 +1,108 @@ +import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; +import { exp, proposal } from '../../../../src/deploy'; +import { Contract, ethers } from 'ethers'; +import { expect } from 'chai'; + +const FRANCHAISER_FACTORY = '0xcd94088b74391dc0a22f0a9611a66dc44f70da72'; + +const chosenAddresses = [ + '0x123ab195dd38b1b40510d467a6a359b201af056f', + '0x321ab195dd38b1b40510d467a6a359b201af056f', + '0x456ab195dd38b1b40510d467a6a359b201af056f', + '0x654ab195dd38b1b40510d467a6a359b201af056f', + '0x789ab195dd38b1b40510d467a6a359b201af056f', +]; + +const amounts = [ + exp(5_000, 18), + exp(10_000, 18), + exp(15_000, 18), + exp(20_000, 18), + exp(25_000, 18), +]; + +export default migration('1722358576_launch_franchaisers', { + prepare: async () => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + + const { + governor, + comptrollerV2, + COMP, + timelock + } = await deploymentManager.getContracts(); + + const franchaiserFactory = new Contract( + FRANCHAISER_FACTORY, + [ + 'function fundMany(address[] calldata delegatees, uint256[] calldata amounts) external returns (address[] memory franchisers)', + 'function getFranchiser(address owner, address delegatee) public view returns (address)', + ], + ethers.getDefaultProvider(1) + ); + + const totalAmount = amounts.reduce((accumulator, currentValue) => accumulator + currentValue, 0n); + + const actions = [ + // 1. Transfer COMP + { + contract: comptrollerV2, + signature: '_grantComp(address,uint256)', + args: [timelock.address, totalAmount], + }, + // 2. Approve FranchaiserFactory + { + contract: COMP, + signature: 'approve(address,uint256)', + args: [FRANCHAISER_FACTORY, totalAmount], + }, + // 3. Fund many + { + contract: franchaiserFactory, + signature: 'fundMany(address[],uint256[])', + args: [chosenAddresses, amounts], + }, + ]; + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry( + async () => trace((await governor.propose(...await proposal(actions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { + COMP, + } = await deploymentManager.getContracts(); + + const franchaiserFactory = new Contract( + FRANCHAISER_FACTORY, + [ + 'function fundMany(address[] calldata delegatees, uint256[] calldata amounts) external returns(address[] memory franchisers)', + 'function getFranchiser(address,address) external view returns(address)', + ], + ethers.getDefaultProvider(1) + ); + + for (let i = 0; i < chosenAddresses.length; i++) { + const franchaiserAddress = await deploymentManager.retry( + async () => await franchaiserFactory.getFranchiser('0x6d903f6003cca6255D85CcA4D3B5E5146dC33925', chosenAddresses[i]) + ); + expect(franchaiserAddress).to.be.not.equal(ethers.constants.AddressZero); + expect(await COMP.balanceOf(franchaiserAddress)).to.be.equal(amounts[i]); + } + + }, +}); From 8c9f4b032db13a12baa75aec02c194508f5ebaf0 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 31 Jul 2024 17:14:43 +0300 Subject: [PATCH 2/6] feat: add votes check --- .../1722358576_launch_franchaisers.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts index 6e81f2935..4665972a2 100644 --- a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -6,11 +6,11 @@ import { expect } from 'chai'; const FRANCHAISER_FACTORY = '0xcd94088b74391dc0a22f0a9611a66dc44f70da72'; const chosenAddresses = [ - '0x123ab195dd38b1b40510d467a6a359b201af056f', - '0x321ab195dd38b1b40510d467a6a359b201af056f', - '0x456ab195dd38b1b40510d467a6a359b201af056f', - '0x654ab195dd38b1b40510d467a6a359b201af056f', - '0x789ab195dd38b1b40510d467a6a359b201af056f', + '0x4Ac0Dbce527bcb60787CEF10053348B146C6b5e3', + '0xE13C54214267675428Adf7E0af9DA433F5Ead460', + '0xb55a948763e0d386b6dEfcD8070a522216AE42b1', + '0x3FB19771947072629C8EEE7995a2eF23B72d4C8A', + '0x7B3c54e17d618CC94daDFe7671c1e2F50C4Ecc33', ]; const amounts = [ @@ -21,6 +21,8 @@ const amounts = [ exp(25_000, 18), ]; +const votesBefore: bigint[] = []; + export default migration('1722358576_launch_franchaisers', { prepare: async () => { return {}; @@ -68,6 +70,7 @@ export default migration('1722358576_launch_franchaisers', { }, ]; const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) ); @@ -102,6 +105,11 @@ export default migration('1722358576_launch_franchaisers', { ); expect(franchaiserAddress).to.be.not.equal(ethers.constants.AddressZero); expect(await COMP.balanceOf(franchaiserAddress)).to.be.equal(amounts[i]); + expect( + await COMP.getCurrentVotes( + chosenAddresses[i] + ) + ).to.be.equal(ethers.BigNumber.from(votesBefore[i]).add(amounts[i])); } }, From a27e954bea9d477fdae1dec8fafdff192b1979d0 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 31 Jul 2024 17:55:50 +0300 Subject: [PATCH 3/6] fix: fill up array --- .../usdc/migrations/1722358576_launch_franchaisers.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts index 4665972a2..572a18094 100644 --- a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -75,6 +75,12 @@ export default migration('1722358576_launch_franchaisers', { async () => trace((await governor.propose(...await proposal(actions, description)))) ); + for (let i = 0; i < chosenAddresses.length; i++) { + votesBefore.push(await COMP.getCurrentVotes( + chosenAddresses[i] + )); + } + const event = txn.events.find(event => event.event === 'ProposalCreated'); const [proposalId] = event.args; @@ -96,7 +102,7 @@ export default migration('1722358576_launch_franchaisers', { 'function fundMany(address[] calldata delegatees, uint256[] calldata amounts) external returns(address[] memory franchisers)', 'function getFranchiser(address,address) external view returns(address)', ], - ethers.getDefaultProvider(1) + ethers.getDefaultProvider() ); for (let i = 0; i < chosenAddresses.length; i++) { From 205ca67c0e41bc63272235c08f5779f95e3d9252 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 20 Aug 2024 16:59:55 +0300 Subject: [PATCH 4/6] feat: new factory and config for distribution --- .../1722358576_launch_franchaisers.ts | 36 ++++++++++++------- scenario/constraints/ProposalConstraint.ts | 6 ++-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts index 572a18094..9d8f317d3 100644 --- a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -3,22 +3,30 @@ import { exp, proposal } from '../../../../src/deploy'; import { Contract, ethers } from 'ethers'; import { expect } from 'chai'; -const FRANCHAISER_FACTORY = '0xcd94088b74391dc0a22f0a9611a66dc44f70da72'; +const FRANCHAISER_FACTORY = '0xE696d89f4F378772f437F01FaaD70240abdf1854'; const chosenAddresses = [ - '0x4Ac0Dbce527bcb60787CEF10053348B146C6b5e3', - '0xE13C54214267675428Adf7E0af9DA433F5Ead460', - '0xb55a948763e0d386b6dEfcD8070a522216AE42b1', - '0x3FB19771947072629C8EEE7995a2eF23B72d4C8A', - '0x7B3c54e17d618CC94daDFe7671c1e2F50C4Ecc33', + '0x3FB19771947072629C8EEE7995a2eF23B72d4C8A', // PGov + '0x070341aA5Ed571f0FB2c4a5641409B1A46b4961b', // Franklin DAO + '0x0579A616689f7ed748dC07692A3F150D44b0CA09', // Arana Digital + '0x13BDaE8c5F0fC40231F0E6A4ad70196F59138548', // Michigan Blockchain + '0x66cD62c6F8A4BB0Cd8720488BCBd1A6221B765F9', // Allthecolors + '0xB49f8b8613bE240213C1827e2E576044fFEC7948', // Avantgarde Finance + '0xb35659cbac913D5E4119F2Af47fD490A45e2c826', // Event Horizon + '0x72C58877ef744b86F6ef416a3bE26Ec19d587708', // sharp + '0x4f894Bfc9481110278C356adE1473eBe2127Fd3C', // Alpha Growth ]; const amounts = [ - exp(5_000, 18), - exp(10_000, 18), - exp(15_000, 18), - exp(20_000, 18), - exp(25_000, 18), + exp(34_450, 18), + exp(9_999.76, 18), + exp(50_000, 18), + exp(29_999.88, 18), + exp(44_371.81, 18), + exp(29_999.85, 18), + exp(50_000, 18), + exp(1_178.7, 18), + exp(50_000, 18), ]; const votesBefore: bigint[] = []; @@ -44,7 +52,7 @@ export default migration('1722358576_launch_franchaisers', { 'function fundMany(address[] calldata delegatees, uint256[] calldata amounts) external returns (address[] memory franchisers)', 'function getFranchiser(address owner, address delegatee) public view returns (address)', ], - ethers.getDefaultProvider(1) + deploymentManager.hre.ethers.provider ); const totalAmount = amounts.reduce((accumulator, currentValue) => accumulator + currentValue, 0n); @@ -102,8 +110,10 @@ export default migration('1722358576_launch_franchaisers', { 'function fundMany(address[] calldata delegatees, uint256[] calldata amounts) external returns(address[] memory franchisers)', 'function getFranchiser(address,address) external view returns(address)', ], - ethers.getDefaultProvider() + deploymentManager.hre.ethers.provider ); + const totalAmount = amounts.reduce((accumulator, currentValue) => accumulator + currentValue, 0n); + expect(totalAmount).to.be.equal(exp(300_000, 18)); for (let i = 0; i < chosenAddresses.length; i++) { const franchaiserAddress = await deploymentManager.retry( diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 02ccc7407..141df1d66 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,9 +62,9 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 259 - if (proposal.id.eq(259)) { - console.log('Skipping proposal 259'); + // temporary hack to skip proposal 307 + if (proposal.id.eq(307)) { + console.log('Skipping proposal 307'); continue; } From d2e5b14f37654688dac54570d2351963437f6ca3 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 20 Aug 2024 21:04:24 +0300 Subject: [PATCH 5/6] feat: add description --- .../mainnet/usdc/migrations/1722358576_launch_franchaisers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts index 9d8f317d3..70c03d450 100644 --- a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -77,7 +77,7 @@ export default migration('1722358576_launch_franchaisers', { args: [chosenAddresses, amounts], }, ]; - const description = 'DESCRIPTION'; + const description = '# Finalize Delegate Race (Cycle 1)\n\n## Summary:\n\nThe Compound Governance Support Working Group (GSWG) introduced the DAO’s first delegate race in July to increase the robustness of the DAO’s delegate base. This onchian proposal is the final step for ratifying delegation to the 8 entities/individuals that received the highest number of points during the application process. If this proposal passes, 8 Franchiser contracts will be created by the already deployed and audited FranchiserFactory contract, ownership of the FranchiserFactory will be set to the timelock, and each individual Franchiser will allocate the appropriate amount of voting power to the delegates’ addresses. A total of 250k COMP will be delegated from the Comptroller to the elected delegates. This vote also includes 50k COMP in delegation to Alpha Growth since their team did not receive delegation from a previously passed proposal due to a lack of the stated Franchiser infrastructure. Hence, the total number of Franchisers will amount to 9, and the delegated COMP will amount to 300k.\n\n## Proposal Motivation:\n\nDespite Compound being one of the most prominent DeFi protocols, this stature unfortunately does not translate into active governance participation. Many top delegates with considerable voting power have less than 50% vote participation rate–and many even sit under 10%. In healthy governance environments, proactive delegates wield significant voting power, ensuring malicious votes are prevented and quorum requirements are met. This is especially vital for lending markets with a high degree of reliance on governance, requiring delegates to be consistently available, often voting on numerous proposals over the course of a single week. The recent governance attack also illustrated the importance of allocating more voting power to community-trusted delegates.\n\nMost DAOs have dormant native tokens present in their treasury. If these native tokens aren’t already being allocated to active initiatives, they can be put to good use elsewhere. There is minimal risk to setting aside a pocket of these dormant funds to ensure better governance participation in a manner where governance can recall the tokens back to the community treasury. The most straightforward means by which these tokens can be mobilized is by delegating a portion of them to either existing or new delegates.\n\nFor sourcing the votes, we can reference Compound’s [Comptroller](https://etherscan.io/address/0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b):\n\n\t- Address: 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B\n\t- As of August 20, 2024 the address held 1,578,587.6 COMP tokens\n\t- Dormant capital from this address can be mobilized for delegation\n\nIt is important to establish a structure that will enable the DAO to sustain control and ownership of these funds, however. In other words, if the DAO would like to clawback funds at any point, either to use the tokens for alternative purposes, or to rescind a particular wallet’s voting power, it should be able to do so seamlessly. The advantage of this setup is that the DAO’s COMP tokens never leave the community’s control and can be relocated when needed at any time.\n\n## More details on the implementation:\n\nThis proposal will delegate voting power to the 8 applicants using the deployed [FranchiserFactory](https://etherscan.io/address/0xE696d89f4F378772f437F01FaaD70240abdf1854), which is responsible for creating Franchiser contracts. The Franchiser contracts have already been [audited by OpenZeppelin](https://forum.arbitrum.foundation/t/event-horizon-franchiser-contract-audit/25738). Individual Franchiser contracts are used to delegate COMP tokens to noted addresses, and the FranchiserFactory has the ability to clawback the voting power if it decides. Ownership of the FranchiserFactory will be given to the Compound timelock, effectively enabling the DAO to give or take voting power from elected delegates. Hence, revoking voting power once it has been granted to the given addresses requires a typical onchain governance vote.\n\n## Delegate Race Outcome:\n\nThe GSWG created a [points system](https://www.comp.xyz/t/compound-delegate-race/5460) to decide how points would be given to delegates. This setup weighted heavily on a wallet’s voting participation rate–and also rewarded those who authored and sponsored previous proposals, as well as attended community calls. A week-long [application process](https://www.comp.xyz/t/compound-delegate-race-application/5521) followed, with 18 groups and individuals applying for delegation. Up to 50k COMP delegation for each applicant was given–unless that address hit 80k COMP in total delegation, at which point, the upper threshold for voting power was capped. This process was conducted until the 250l allocation was fully used. There was a tie breaker between four candidates, and votes were allocated based on which candidate had the oldest active delegate address.\n\nBelow are the final candidates, along with their assigned votes and voting addresses.\n\n\n### Candidate: PGov\n\t- Points Scored: 10\n\t- Assigned Votes: 34,450.00\n\t- Voting Power Final: 80,000.00\n\t- Address: 0x3FB19771947072629C8EEE7995a2eF23B72d4C8A\n\n### Candidate: Franklin DAO\n\t- Points Scored: 9\n\t- Assigned Votes: 9,999.76\n\t- Voting Power Final: 80,000.00\n\t- Address: 0x070341aA5Ed571f0FB2c4a5641409B1A46b4961b\n\n### Candidate: Arana Digital\n\t- Points Scored: 9\n\t- Assigned Votes: 50,000.00\n\t- Voting Power Final: 50,001.00\n\t- Address: 0x0579A616689f7ed748dC07692A3F150D44b0CA09\n\n### Candidate: Michigan Blockchain\n\t- Points Scored: 8\n\t- Assigned Votes: 29,999.88\n\t- Voting Power Final: 80,000.00\n\t- Address: 0x13BDaE8c5F0fC40231F0E6A4ad70196F59138548\n\n### Candidate: Allthecolors\n\t- Points Scored: 6\n\t- Assigned Votes: 44,371.81\n\t- Voting Power Final: 80,000.00\n\t- Address: 0x66cD62c6F8A4BB0Cd8720488BCBd1A6221B765F9\n\n### Candidate: Avantgarde Finance\n\t- Points Scored: 6\n\t- Assigned Votes: 29,999.85\n\t- Voting Power Final: 80,000.00\n\t- Address: 0xB49f8b8613bE240213C1827e2E576044fFEC7948\n\n### Candidate: Event Horizon\n\t- Points Scored: 6\n\t- Assigned Votes: 50,000.00\n\t- Voting Power Final: 54,588.74\n\t- Address: 0xb35659cbac913D5E4119F2Af47fD490A45e2c826\n\n### Candidate: Sharp\n\t- Points Scored: 6\n\t- Assigned Votes: 1,178.70\n\t- Voting Power Final: 1,190.67\n\t- Address: 0x72C58877ef744b86F6ef416a3bE26Ec19d587708'; const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) From 04ac594488f5d27b2a8ccaca2c2564252646c970 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Mon, 26 Aug 2024 16:40:22 +0000 Subject: [PATCH 6/6] Modified migration from GitHub Actions --- .../mainnet/usdc/migrations/1722358576_launch_franchaisers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts index 70c03d450..d6d0c23e4 100644 --- a/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts +++ b/deployments/mainnet/usdc/migrations/1722358576_launch_franchaisers.ts @@ -95,8 +95,8 @@ export default migration('1722358576_launch_franchaisers', { trace(`Created proposal ${proposalId}.`); }, - async enacted(): Promise { - return false; + async enacted(deploymentManager: DeploymentManager): Promise { + return true; }, async verify(deploymentManager: DeploymentManager) {