Reverie ha elaborado una guía técnica completa para enviar una propuesta de gobernanza para transferir ethDYDX de la Tesorería de la comunidad a través de un Pull Request (PR) al repositorio de contratos de gobernanza de dYdX.
Para crear esta propuesta, un miembro de la comunidad dYdX debe tener **al menos 5M de tokens de gobernanza **(_0,5 % del suministro total) _de poder de propuesta (umbral de propuesta para un voto de bloqueo de tiempo corto).
Requisitos preliminares
Se deben cumplir los siguientes pasos antes de presentar la solicitud de retiro (SR):
Ciclo de vida de la propuesta: El DRC debe publicarse siguiendo la plantilla de la propuesta y debe haber un voto de Snapshot exitoso.
Dirección de destino: La dirección de destino debe generarse con antelación. Si la dirección de destino es multifirmas, se debe crear la billetera multifirmas.
Cuenta de GitHub: Una cuenta de GitHub para bifurcar el repositorio.
Cantidad de la transferencia (opcional): Es preferible que se establezca la cantidad solicitada de la transferencia antes de la SR. Sin embargo, si se usa una cantidad hipotética, se puede establecer como paso final antes de la aprobación.
Hash IPFS de DIP (opcional): Si se conoce la cantidad de la transferencia, se debe finalizar el DIP y enviarse a IPFS para generar su hash. Sin embargo, esto se puede establecer como paso final antes de la aprobación si la cantidad aún no se determina.
En src/config/index.ts, agregue dos variables nuevas a la constante configSchema que se usará para fines de hacer pruebas. En los siguientes bloques de código, cambie los campos 'PROPOSAL_NAME' y 'PROPOSAL' al nombre de la propuesta que se va a presentar.
Nota: La cantidad de financiamiento se tendrá que multiplicar por 10^18 conforme a la norma ERC20. Si la cantidad aún no se conoce, se puede usar una temporal (p. ej. 10 → 10000000000000000000).
En src/lib/constants.ts, agregue la variable del hash de IPFS que hará referencia al DIP aprobado en el otro repositorio:
src/lib/constants.ts...// Add a link to where the hash can be foundexportconstDIP_NUMBER_IPFS_HASH='0x0000000000000000000000000000000000000000000000000000000000000000';
Nota: Si el DIP aún no se ha publicado, se puede utilizar un valor temporal para hacer pruebas (p. ej., ‘0x0000000000000000000000000000000000000000000000000000000000000000’)\
4. Código de la propuesta
En src/migrations, cree un nuevo archivo con el nombre de la propuesta → proposal-name.ts y llénelo con el siguiente código:
a. Agregue las importaciones necesarias en la parte superior:
b. Cree una nueva función usando el nombre de la propuesta debajo de las importaciones y agregue el siguiente código con dos variables únicas:
destinationAddress → esta será la dirección que reciba el financiamiento
deployConfig.PROPOSAL_FUNDING_AMOUNT → esta es la variable que creamos antes y que determinará la cantidad que va a transferirse
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. Tarea de despliegue
Con la propuesta creada, podemos escribir el despliegue que generará transacción y los datos de llamada necesarios para presentar la propuesta.
En tasks/deployment, cree un nuevo archivo con el mismo nombre que se usó para el código de la propuesta → proposal-name.ts y llénelo con el siguiente código:
a. Agregue las importaciones necesarias con las siguientes variables:
DIP_NUMBER_IPFS_HASH → esta es la variable que agregamos en lib/constants
createProposalNameProposal → esta es la función que creamos en /src/migrations/proposal-name
b. Cree la tarea hardhat y llénela con la información de la propuesta en la línea de apertura de la tarea.
Escriba el nombre de la propuesta en deploy:proposal-name: y agregue una breve descripción en “Proposal Description”.
La última línea llama a la función que importó del código de la propuesta, así que tendrá que ajustarse.
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. Elaboración de las pruebas
Ahora que el código está listo para desplegarse, es hora de elaborar algunas pruebas en función de la propuesta. Las pruebas se hacen tanto de manera local como con una bifurcación de la mainnet para simular la ejecución de una propuesta en cadena.
a. Agregue pruebas de propuesta
En test/migrations, agregue una vez más un archivo nuevo con el nombre de la propuesta → proposal-name.ts e incluya el siguiente código:
Agregue las importaciones necesarias, incluidas las funciones de la propuesta:
createProposalNameProposal → esta es la función que creamos en /src/migrations/proposal-name \
MOCK_PROPOSAL_IPFS_HASH → vamos a usar un hash hipotético para fines de las pruebas
Agregue pruebas para ambas funciones creando una función general para pruebas → execute executeProposalNameProposalForTest; cambie el nombre para que coincida con el de la propuesta.
También llamamos a la variable config TEST_PROPOSAL_NAME_TRUST_WITH_PROPOSAL previamente creada y la variable PROPOSAL_NAME_ADDRESS 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. Agregue el contrato a los ayudantes de la prueba
En test/helpers/get-deployed-contracts-for-test.ts, agregue la función creada antes para que las pruebas se lleven a cabo en las pruebas de la bifurcación de la mainnet:
Importe la función executeProposalNameProposalForTest del archivo de migraciones:
Agregue la función a la función getDeployedContractsForTest(), fuera del último bucle “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. Archivo final de prueba
Por último, agregamos una prueba tanto del hash IPFS y el saldo de la billetera multifirmas luego de la propuesta hipotética para asegurarse de que todo resulte como se espera.
En test/misc, agregue un nuevo archivo con el nombre de la propuesta etiquetado → proposal-name-proposal.spec.ts y llénelo con estas dos pruebas:
Importamos el hash IPFS de lib por medio de DIP_NUMBER_IPFS_HASH
preprogramamos el siguiente número de proposalId usando ProposalNameId
revisamos el hash de la propuesta con la constante Hash
revisamos PROPOSAL_NAME_ADDRESS para comprobar que tenga el saldo esperado de PROPOSAL_FUNDING_AMOUNT
Nota: Si esta dirección ya tiene DYDX, tendrá que preprogramarlo en el saldo para pasar la prueba.
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. Presentación de la SR
Una vez que todos estos cambios en el código se lleven a cabo y se guarden localmente, podemos confirmarlos en el repositorio bifurcado y abrir una SR al repositorio de dYdX para su revisión:
a. Confirme los cambios por medio de la línea de comandos