Пользователь Reverie подготовил исчерпывающее техническое руководство по представлению предложения по управлению для перевода $ethDYDX из кассы сообщества с помощью запроса на внесение изменений в хранилище контрактов на управление dYdX.
Перед подачей запроса на внесение изменений необходимо сделать следующее:
Жизненный цикл предложения. Нужно опубликовать ЗК согласно шаблону предложения, а члены сообщества должны проголосовать за предложение на Snapshot.
Адрес назначения. Необходимо заранее создать адрес назначения. Если адрес назначения требует мультиподписи, нужно создать кошелек с мультиподписью.
Счет GitHub. Необходим счет GitHub для создания форка хранилища.
Количество переводимых средств (необязательно). Желательно установить переводимое количество средств до отправки запроса на внесение изменений. Однако если это значение условное, то количество средств можно установить прямо перед утверждением.
Хэш IPFS ППУ (необязательно). Если известно количество переводимых средств, то ППУ необходимо оформить и отправить в IPFS для генерации хэша. Однако если количество средств еще не определено, это можно сделать прямо перед утверждением.
Добавьте две новые переменные в константу configSchema в файле src/config/index.ts. Они будут использоваться для тестирования. В следующих блоках кода измените поля PROPOSAL_NAME и PROPOSAL на название отправляемого предложения.
Примечание. Согласно стандарту ERC-20 количество переводимых средств нужно умножить на 10^18. Если количество средств еще неизвестно, можно указать временное значение (например, 10 → 10000000000000000000).
Добавьте переменную хэша IPFS, которая будет ссылаться на ППУ в другом репозитории, в файл src/lib/constants.ts:
src/lib/constants.ts...// Add a link to where the hash can be foundexportconstDIP_NUMBER_IPFS_HASH='0x0000000000000000000000000000000000000000000000000000000000000000';
Примечание. Если ППУ еще не было опубликовано, для тестирования можно указать временное значение (например, ‘0x0000000000000000000000000000000000000000000000000000000000000000’)\
4. Код предложения
Создайте новый файл с названием предложения (proposal-name.ts) в файле src/migrations, а затем внесите в него следующий код:
а) Добавьте функции импорта в верхней части файла:
б) Создайте новую функцию, используя название предложения, ниже функций импорта и добавьте следующий код с двумя уникальными переменными:
destinationAddress — это адрес, который получит переводимые средства.
deployConfig.PROPOSAL_FUNDING_AMOUNT — это созданная ранее переменная, которая определит количество переводимых средств.
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. Развертывание
Создав предложение, мы можем написать код развертывания, который будет генерировать транзакцию и данные вызова, необходимые для отправки предложения.
В разделе tasks/deployment создайте новый файл с тем же названием, что использовалось для кода предложения (proposal-name.ts), и внесите в него следующий код:
а) Добавьте необходимые функции импорта со следующими переменными:
DIP_NUMBER_IPFS_HASH — это переменная, которую мы добавляем в раздел lib/constants.
createProposalNameProposal — это функция, которую мы создали в разделе /src/migrations/proposal-name.
б) Создайте задачу hardhat и внесите информацию о предложении в ее первую строку.
Укажите название предложения вместо proposal-name в ‘deploy:proposal-name:’, а затем введите краткое описание в ‘Proposal Description’.
Последняя строка вызывает функцию, которую вы импортировали из кода предложения, поэтому ее нужно будет скорректировать.
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. Создание тестов
Теперь, когда код готов к развертыванию, пришло время создать несколько тестов для предложения. Тестирование проводится как локально, так и с помощью форка мейннета для моделирования выполнения предложения в цепочке.
а) Добавьте тесты предложения
Снова добавьте новый файл с названием предложения в раздел test/migrations и внесите в него следующий код:
Добавьте необходимые функции импорта, включая функции предложения:
createProposalNameProposal — это функция, которую мы создали в разделе /src/migrations/proposal-name. \
MOCK_PROPOSAL_IPFS_HASH — это имитация хэша для тестирования.
Добавьте тесты для обеих функций, создав общую функцию тестирования executeProposalNameProposalForTest. Переименуйте ее так, чтобы ее название совпадало с названием предложения.
Мы также вызываем ранее созданную переменную конфигурации TEST_PROPOSAL_NAME_TRUST_WITH_PROPOSAL и PROPOSAL_NAME_ADDRESS из 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
в) Добавьте контракт для тестирования
Добавьте функцию, созданную выше, в файл test/helpers/get-deployed-contracts-for-test.ts, чтобы тесты запускались в рамках тестирования форка мейннета:
Импортируйте функцию executeProposalNameProposalForTest из файла переноса:
Добавьте эту функцию в getDeployedContractsForTest() вне последнего цикла 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;}
г) Окончательный тестовый файл
Наконец, мы добавляем проверку хэша IPFS и баланса мультиподписи после имитации предложения, чтобы убедиться, что все завершится так, как ожидалось.
Добавьте новый файл proposal-name-proposal.spec.ts с названием предложения в раздел test/misc, а затем включите в него следующие два теста:
Мы импортируем хэш IPFS из lib с помощью DIP_NUMBER_IPFS_HASH.
Мы жестко кодируем следующий номер proposalId с помощью ProposalNameId.
Мы проверяем хэш предложения с помощью константы Hash.
Мы проверяем PROPOSAL_NAME_ADDRESS на предмет наличия ожидаемого баланса PROPOSAL_FUNDING_AMOUNT.
Примечание. Если у этого адреса уже есть DYDX, вам потребуется жестко закодировать баланс для завершения теста.
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. Отправка запроса на внесение изменений
После внесения всех изменений в код и их локального сохранения мы можем зафиксировать форк репозитория и отправить запрос на внесение изменений в репозиторий dYdX для рассмотрения:
а) Зафиксируйте изменения с помощью командной строки.