src/lib/constants.ts
...
// Add a link to where the hash can be found
export const DIP_NUMBER_IPFS_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000';
src/migrations/proposal-name.ts
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import {
DydxGovernor__factory,
} from '../../types';
import { getDeployConfig } from '../deploy-config';
import { getDeployerSigner } from '../deploy-config/get-deployer-address';
import { getHre } from '../hre';
import { log } from '../lib/logging';
import { waitForTx } from '../lib/util';
import { Proposal } from '../types';
tasks/deployment/proposal-name.ts
import { types } from 'hardhat/config';
import mainnetAddresses from '../../src/deployed-addresses/mainnet.json';
import { hardhatTask } from '../../src/hre';
import { DIP_NUMBER_IPFS_HASH } from '../../src/lib/constants';
import { createProposalNameProposal } from '../../src/migrations/proposal-name';
tasks/deployment/proposal-name.ts
hardhatTask('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) => {
await createProposalNameProposal(args);
});
test/migrations/proposal-name.ts
import BNJS from 'bignumber.js';
import { BigNumber, BigNumberish } from 'ethers';
import config from '../../src/config';
import { getDeployConfig } from '../../src/deploy-config';
import { getDeployerSigner } from '../../src/deploy-config/get-deployer-address';
import { log } from '../../src/lib/logging';
import { waitForTx } from '../../src/lib/util';
import { impersonateAndFundAccount } from '../../src/migrations/helpers/impersonate-account';
import { createProposalNameProposal } from '../../src/migrations/proposal-name';
import {
DydxGovernor__factory,
DydxToken__factory,
Treasury__factory,
} from '../../types';
import { advanceBlock, increaseTimeAndMine } from '../helpers/evm';
const MOCK_PROPOSAL_IPFS_HASH = (
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
test/migrations/proposal-name.ts
export async function fundProposalNameViaProposal({
dydxTokenAddress,
governorAddress,
shortTimelockAddress,
communityTreasuryAddress,
destinationAddress,
}: {
dydxTokenAddress: string,
governorAddress: string,
shortTimelockAddress: string,
communityTreasuryAddress: string,
destinationAddress: string,
}): Promise<void> {
const deployConfig = getDeployConfig();
const deployer = await getDeployerSigner();
const dydxToken = new DydxToken__factory(deployer).attach(dydxTokenAddress);
const governor = new DydxGovernor__factory(deployer).attach(governorAddress);
await fundCommunityTreasuryFromFoundationIfNecessary({
dydxTokenAddress,
communityTreasuryAddress,
minTreasuryBalance: deployConfig.PROPOSAL_FUNDING_AMOUNT,
});
// Pick a voter with enough tokens to meet the quorum requirement.
const voterAddress = deployConfig.TOKEN_ALLOCATIONS.DYDX_TRADING.ADDRESS;
const voter = await impersonateAndFundAccount(voterAddress);
const voterBalance = await dydxToken.balanceOf(voterAddress);
if (voterBalance.lt(new BNJS('2e25').toFixed())) {
throw new Error('Not enough votes to pass the proposal.');
}
// Vote on an existing proposal (can be used with mainnet forking).
let proposalId: BigNumberish;
if (config.FUND_PROPOSAL_NAME_PROPOSAL_ID !== null) {
proposalId = config.FUND_PROPOSAL_NAME_PROPOSAL_ID;
} else {
log('Creating proposal');
({ proposalId } = await createProposalNameProposal({
proposalIpfsHashHex: MOCK_PROPOSAL_IPFS_HASH,
dydxTokenAddress,
governorAddress,
shortTimelockAddress,
communityTreasuryAddress,
destinationAddress,
signer: voter,
}));
log('Waiting for voting to begin');
for (let i = 0; i < deployConfig.VOTING_DELAY_BLOCKS + 1; i++) {
if (i > 0 && i % 2000 === 0) {
log('mining', i);
}
await advanceBlock();
}
}
let proposalState = await governor.getProposalState(proposalId);
if (proposalState !== 2) {
throw new Error('Expected proposal to be in the voting phase.');
}
log('Submitting vote');
await waitForTx(await governor.connect(voter).submitVote(proposalId, true));
log('Waiting for voting to end');
let minedCount = 0;
for (; ;) {
for (let i = 0; i < 2000; i++) {
await advanceBlock();
minedCount++;
}
log('mining', minedCount);
proposalState = await governor.getProposalState(proposalId);
if (proposalState !== 2) {
break;
}
}
if (proposalState !== 4) {
throw new Error(`Expected proposal to have succeeded but state was ${proposalState}`);
}
log('Queueing the proposal');
await waitForTx(await governor.queue(proposalId));
const delaySeconds = deployConfig.SHORT_TIMELOCK_CONFIG.DELAY;
await increaseTimeAndMine(delaySeconds);
log('Executing the proposal');
await waitForTx(await governor.execute(proposalId));
log('Proposal executed');
log('\n=== FUNDING PROPOSAL COMPLETE ===\n');
}
export async function fundProposalNameNoProposal({
dydxTokenAddress,
shortTimelockAddress,
communityTreasuryAddress,
destinationAddress,
}: {
dydxTokenAddress: string,
shortTimelockAddress: string,
communityTreasuryAddress: string,
destinationAddress: string,
}): Promise<void> {
const deployConfig = getDeployConfig();
const mockShortTimelock = await impersonateAndFundAccount(shortTimelockAddress);
const communityTreasury = new Treasury__factory(mockShortTimelock).attach(
communityTreasuryAddress,
);
await fundCommunityTreasuryFromFoundationIfNecessary({
dydxTokenAddress,
communityTreasuryAddress,
minTreasuryBalance: deployConfig.PROPSAL_FUNDING_AMOUNT,
});
await waitForTx(
await communityTreasury.transfer(
dydxTokenAddress,
destinationAddress,
deployConfig.PROPOSAL_FUNDING_AMOUNT,
),
);
log('\n=== PROPOSAL FUNDING COMPLETE ===\n');
}
async function fundCommunityTreasuryFromFoundationIfNecessary({
dydxTokenAddress,
communityTreasuryAddress,
minTreasuryBalance,
}: {
dydxTokenAddress: string,
communityTreasuryAddress: string,
minTreasuryBalance: string,
}): Promise<void> {
const deployConfig = getDeployConfig();
const mockFoundation = await impersonateAndFundAccount(deployConfig.TOKEN_ALLOCATIONS.DYDX_FOUNDATION.ADDRESS);
const dydxToken = new DydxToken__factory(mockFoundation).attach(dydxTokenAddress);
const communityTreasuryBalance: BigNumber = await dydxToken.balanceOf(communityTreasuryAddress);
if (communityTreasuryBalance.lt(minTreasuryBalance)) {
// Transfer necessary funds to the treasury.
await waitForTx(
await dydxToken.transfer(
communityTreasuryAddress,
minTreasuryBalance,
),
);
}
}
test/migrations/deploy-contracts-for-test.ts
...
import { fundProposalNameNoProposal, fundProposalNameViaProposal } from './proposal-name-proposal';
...
export async function executeProposalNameProposalForTest(
deployedContracts: AllDeployedContracts,
) {
const deployConfig = getDeployConfig();
if (config.TEST_PROPOSAL_NAME_TRUST_WITH_PROPOSAL) {
await fundProposalNameViaProposal({
dydxTokenAddress: deployedContracts.dydxToken.address,
governorAddress: deployedContracts.governor.address,
shortTimelockAddress: deployedContracts.shortTimelock.address,
communityTreasuryAddress: deployedContracts.communityTreasury.address,
destinationAddress: deployConfig.PROPOSAL_NAME_ADDRESS,
});
} else {
await fundProposalNameNoProposal({
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
async function getDeployedContractsForTest(): Promise<AllDeployedContracts> {
if (!config.isHardhat()) {
return getAllContracts();
}
let deployedContracts: AllDeployedContracts;
if (config.FORK_MAINNET) {
deployedContracts = await getAllContracts();
} else {
deployedContracts = await deployContractsForTest();
// 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.
await executeSafetyModuleRecoveryProposalsForTest(deployedContracts);
await executeStarkProxyProposalForTest(deployedContracts);
await executeGrantsProgramProposalForTest(deployedContracts);
await executeGrantsProgramv15ProposalForTest(deployedContracts);
await executeWindDownBorrowingPoolProposalForTest(deployedContracts);
await executeUpdateMerkleDistributorRewardsParametersProposalForTest(deployedContracts);
await executeWindDownSafetyModuleProposalForTest(deployedContracts);
}
await executeProposalNameProposalForTest(deployedContracts);
// Execute the proposals which have not yet been executed on mainnet.
await configureForTest(deployedContracts);
return deployedContracts;
}
test/misc/proposal-name-proposal.spec.ts
import { expect } from 'chai';
import { DIP_NUMBER_IPFS_HASH } from '../../src/lib/constants';
import { describeContract, TestContext } from '../helpers/describe-contract';
function init() {}
describeContract('proposal-name', init, (ctx: TestContext) => {
it('Proposal IPFS hash is correct', async () => {
const ProposalNameId = #;
const proposal = await ctx.governor.getProposalById(ProposalNameId);
expect(proposal.ipfsHash).to.equal(DIP_NUMBER_IPFS_HASH);
});
it('Destination receives tokens from the community treasury', async () => {
const balance = await ctx.dydxToken.balanceOf(ctx.config.PROPOSAL_NAME_ADDRESS);
expect(balance).to.equal(ctx.config.PROPOSAL_FUNDING_AMOUNT);
});
});
Three Arrows CapitalのSu Zhu氏(zhusu)は、流動性プロバイダー報酬のしきい値を引き下げるためのを作成しました。WintermuteのEvgeny氏、KronosのBen氏、SixtantのJosh氏など、さまざまなコミュニティメンバーがディスカッションに参加し、貴重なフィードバックを提供しています。
key_pair_with_y_coordinate = client.onboarding.derive_stark_key(
# Optional if eth_private_key or web3.eth.defaultAccount was provided.
ethereum_address='ethereumAddress',
)
api_key_response = client.api_keys.create_api_key(
# Optional if eth_private_key or web3.eth.defaultAccount was provided.
ethereum_address='0x0123...',
)