Guide technique sur l'élaboration d'une proposition de dépenses de trésorerie de la communauté dYdX
Un guide technique, qui décrit étape par étape, la façon de créer une proposition de transfert ethDYDX de la trésorerie de la communauté vers une adresse de destination.
Reverie a élaboré un guide technique complet pour soumettre une proposition de gouvernance visant à transférer des $ethDYDX du Trésor communautaire par le biais d'une demande d'extraction (PR, Pull Request) vers le référentiel des contrats de gouvernance dYdX.
Pour créer cette proposition, un membre de la communauté dYdX doit avoir au moins 5 millions de jetons de gouvernace(0,5 % de l'offre totale) de pouvoir de proposition (seuil de proposition pour un vote à court terme).
Exigences préliminaires
Les étapes suivantes doivent être accomplies avant l'achèvement de la demande d'extraction (PR) :
Durée de vie de la proposition : la DRC doit être publiée en suivant le modèle de proposition et il doit y avoir un vote Snapshot réussi.
Adresse de destination : l'adresse de destination doit être générée à l'avance. Si l'adresse de destination est un multi-sig, le portefeuille multi-sig doit être créé.
Compte GitHub : un compte GitHub pour forker le référentiel.
Montant du transfert (facultatif) : de préférence, le montant du transfert demandé a été établi avant la PR. Toutefois, si vous utilisiez un montant notionnel, il peut être défini comme une étape finale avant l'approbation.
Hachage DIP IPFS (facultatif) : si le montant du transfert est connu, le DIP doit être finalisé et poussé vers l'IPFS pour générer son hachage. Cependant, cela peut être défini comme une étape finale avant l'approbation si le montant n'est pas encore déterminé.
Dans src/config/index.ts, ajoutez deux nouvelles variables à la constante configSchema qui sera utilisée à des fins de test. Dans les blocs de code suivants, changez les champs 'PROPOSAL_NAME' et 'PROPOSAL' par le nom de la proposition soumise.
Dans src/deploy-config/base-config.ts, ajoutez l'adresse de destination et le montant du transfert en tant que nouvelles variables dans la constante config :
Remarque : Le montant du financement devra être multiplié par 10^18 selon la norme ERC20. Si le montant n'est pas encore connu, un montant temporaire peut être utilisé (par exemple 10 → 10000000000000000000)
Dans src/lib/constants.ts, ajoutez la variable de hachage IPFS qui fera référence au DIP approuvé dans l'autre référentiel :
src/lib/constants.ts...// Add a link to where the hash can be foundexportconstDIP_NUMBER_IPFS_HASH='0x0000000000000000000000000000000000000000000000000000000000000000';
Remarque : Si le DIP n'a pas encore été publié, une valeur temporaire peut être utilisée pour le test (par exemple ‘0x0000000000000000000000000000000000000000000000000000000000000000’)\
4. Code de proposition
Dans src/migrations, créez un nouveau fichier nommé d'après la proposition → proposal-name.ts et remplissez le code suivant :
b. Créez une nouvelle fonction en utilisant le nom de proposition sous les importations et ajoutez le code suivant avec deux variables uniques :
destinationAddress → il s'agit de l'adresse qui recevra le financement
deployConfig.PROPOSAL_FUNDING_AMOUNT → il s'agit de la variable que nous avons créée précédemment qui déterminera le montant à transférer
src/migrations/proposal-name.tsexportasyncfunctioncreateProposalNameProposal({ proposalIpfsHashHex, dydxTokenAddress, governorAddress, shortTimelockAddress, communityTreasuryAddress, destinationAddress,// This is the address that will receive the funding signer,}: { proposalIpfsHashHex:string, dydxTokenAddress:string, governorAddress:string, shortTimelockAddress:string, communityTreasuryAddress:string, destinationAddress:string, signer?:SignerWithAddress,}) {consthre=getHre();constdeployConfig=getDeployConfig();constdeployer= signer ||awaitgetDeployerSigner();constdeployerAddress=deployer.address;log(`Creating proposal with proposer ${deployerAddress}.\n`);constgovernor=awaitnewDydxGovernor__factory(deployer).attach(governorAddress);constproposalId=awaitgovernor.getProposalsCount();constproposal:Proposal= [ shortTimelockAddress, [communityTreasuryAddress], ['0'], ['transfer(address,address,uint256)'], [hre.ethers.utils.defaultAbiCoder.encode( ['address','address','uint256'], [dydxTokenAddress, destinationAddress,deployConfig.PROPOSAL_FUNDING_AMOUNT], )], [false], proposalIpfsHashHex, ];awaitwaitForTx(awaitgovernor.create(...proposal));return { proposalId, };}
5. Tâche de déploiement
Avec la proposition créée, nous pouvons écrire le déploiement qui générera transaction et les données d'appel nécessaires pour soumettre la proposition.
Dans tâches/déploiement, créez un nouveau fichier avec le même nom utilisé pour le code de proposition → proposal-name.ts et remplissez avec le code suivant :
a. Ajoutez les importations nécessaires avec les variables suivantes :
DIP_NUMBER_IPFS_HASH → il s'agit de la variable que nous ajoutons dans lib/constants
createProposalNameProposal → il s'agit de la fonction que nous avons créée dans /src/migrations/proposal name
b. Créez la tâche hardhat et remplissez-la avec les informations de proposition sur la ligne d'ouverture de la tâche.
Remplacez le proposal-name dans ‘deploy:proposal-name: et remplacez-le par une brève description dans ‘Description de la proposition’.
La dernière ligne appelle la fonction que vous avez importée à partir du code de proposition, il faudra donc l'ajuster.
tasks/deployment/proposal-name.tshardhatTask('deploy:proposal-name','Proposal Description.') .addParam('proposalIpfsHashHex', 'IPFS hash for the uploaded DIP describing the proposal', DIP_NUMBER_IPFS_HASH, types.string)
.addParam('dydxTokenAddress', 'Address of the deployed DYDX token contract', mainnetAddresses.dydxToken, types.string)
.addParam('governorAddress', 'Address of the deployed DydxGovernor contract', mainnetAddresses.governor, types.string)
.addParam('shortTimelockAddress', 'Address of the deployed short timelock Executor contract', mainnetAddresses.shortTimelock, types.string)
.addParam('communityTreasuryAddress', 'Address of the deployed community treasury contract', mainnetAddresses.communityTreasury, types.string)
.setAction(async (args) => {awaitcreateProposalNameProposal(args); });
6. Tests de construction
Maintenant que le code est prêt pour le déploiement, il est temps de construire certains tests autour de la proposition. Les tests sont effectués à la fois localement et en utilisant un fork du mainnet pour simuler une proposition en cours d'exécution sur la chaîne.
a. Ajoutez des tests de proposition
Dans test/migrations, ajoutez à nouveau un nouveau fichier avec le nom de proposition → proposal-name.ts et incluez le code suivant :
Ajoutez les importations nécessaires, y compris les fonctions de proposition :
createProposalNameProposal → il s'agit de la fonction que nous avons créée dans /src/migrations/proposal name. \
MOCK_PROPOSAL_IPFS_HASH → nous allons utiliser un hachage fictif à des fins de test
Ajoutez des tests pour les deux fonctions en créant une fonction de test générale → executeProposalNameProposalForTest, remplacez le nom pour correspondre à la proposition
Nous appelons aussi la variable config TEST_PROPOSAL_NAME_TRUST_WITH_PROPOSAL précédemment créée et le PROPOSAL_NAME_ADDRESS à partir de deployConfig
...exportasyncfunctionexecuteProposalNameProposalForTest( deployedContracts:AllDeployedContracts,) {constdeployConfig=getDeployConfig();if (config.TEST_PROPOSAL_NAME_TRUST_WITH_PROPOSAL) {awaitfundProposalNameViaProposal({ dydxTokenAddress:deployedContracts.dydxToken.address, governorAddress:deployedContracts.governor.address, shortTimelockAddress:deployedContracts.shortTimelock.address, communityTreasuryAddress:deployedContracts.communityTreasury.address, destinationAddress:deployConfig.PROPOSAL_NAME_ADDRESS, }); } else {awaitfundProposalNameNoProposal({ dydxTokenAddress:deployedContracts.dydxToken.address, shortTimelockAddress:deployedContracts.shortTimelock.address, communityTreasuryAddress:deployedContracts.communityTreasury.address, destinationAddress:deployConfig.PROPOSAL_NAME_ADDRESS, }); }}...// put this above the configureForTest function
c. Ajoutez un contrat aux aides de test
Dans test/helpers/get-deployed-contracts-for-test.ts, ajoutez la fonction créée ci-dessus afin que les tests soient exécutés dans le test de fork du mainnet :
Importez la fonction executeProposalNameProposalForTest à partir du fichier de migrations :
Ajoutez la fonction à la fonction getDeployedContractsForTest(), en dehors de la dernière boucle else :
test/helpers/get-deployed-contracts-for-test.tsasyncfunctiongetDeployedContractsForTest():Promise<AllDeployedContracts> {if (!config.isHardhat()) {returngetAllContracts(); }let deployedContracts:AllDeployedContracts;if (config.FORK_MAINNET) { deployedContracts =awaitgetAllContracts(); } else { deployedContracts =awaitdeployContractsForTest();// Execute the proposals which have already been executed on mainnet.//// The proposals will be executed when running on a local test network,// but will not be executed when running on a mainnet fork.awaitexecuteSafetyModuleRecoveryProposalsForTest(deployedContracts);awaitexecuteStarkProxyProposalForTest(deployedContracts);awaitexecuteGrantsProgramProposalForTest(deployedContracts);awaitexecuteGrantsProgramv15ProposalForTest(deployedContracts);awaitexecuteWindDownBorrowingPoolProposalForTest(deployedContracts);awaitexecuteUpdateMerkleDistributorRewardsParametersProposalForTest(deployedContracts);awaitexecuteWindDownSafetyModuleProposalForTest(deployedContracts); }awaitexecuteProposalNameProposalForTest(deployedContracts);// Execute the proposals which have not yet been executed on mainnet.awaitconfigureForTest(deployedContracts);return deployedContracts;}
d. Fichier de test final
Enfin, nous ajoutons un test de hachage IPFS et de solde du multisig après la proposition fictive pour assurer que tout se termine comme prévu.
Dans test/misc, ajoutez un nouveau fichier avec le nom de proposition étiquté → proposal-name-proposal.spec.ts et remplissez avec ces deux tests :
Nous importons le hachage IPFS à partir de la lib par DIP_NUMBER_IPFS_HASH
nous codons en dur le numéro de proposalId suivant en utilisant ProposalNameId
nous vérifions le hachage de proposition avec le hachage constant
nous vérifions si le PROPOSAL_NAME_ADDRESS a un solde attendu du PROPOSAL_FUNDING_AMOUNT
Remarque : si cette adresse a déjà la mention DYDX, vous devrez coder en dur dans le solde pour que le test passe
test/misc/proposal-name-proposal.spec.tsimport { expect } from'chai';import { DIP_NUMBER_IPFS_HASH } from'../../src/lib/constants';import { describeContract, TestContext } from'../helpers/describe-contract';functioninit() {}describeContract('proposal-name', init, (ctx:TestContext) => {it('Proposal IPFS hash is correct',async () => {constProposalNameId= #;constproposal=awaitctx.governor.getProposalById(ProposalNameId);expect(proposal.ipfsHash).to.equal(DIP_NUMBER_IPFS_HASH); });it('Destination receives tokens from the community treasury',async () => {constbalance=awaitctx.dydxToken.balanceOf(ctx.config.PROPOSAL_NAME_ADDRESS);expect(balance).to.equal(ctx.config.PROPOSAL_FUNDING_AMOUNT); });});
7. Soumettre le PR
Une fois que tous ces changements de code sont effectués et enregistrés localement, nous pouvons nous engager dans le référentiel forked et ouvrir un PR dans le référentiel dYdX pour examen :
a. Validez les modifications par la ligne de commande