301 lines
12 KiB
TypeScript
301 lines
12 KiB
TypeScript
import { artifacts as erc20Artifacts, ERC20TokenEvents } from '@0x/contracts-erc20';
|
|
import { StakingContract, StakingProxyContract } from '@0x/contracts-staking';
|
|
import { blockchainTests, constants, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
|
import { BigNumber, hexUtils, logUtils } from '@0x/utils';
|
|
import * as _ from 'lodash';
|
|
|
|
import { proposals } from '../src/proposals';
|
|
|
|
import { artifacts } from './artifacts';
|
|
import { ISablierEvents, ZrxTreasuryContract, ZrxTreasuryEvents } from './wrappers';
|
|
|
|
const SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/mzhu25/zeroex-staking';
|
|
const STAKING_PROXY_ADDRESS = '0xa26e80e7dea86279c6d778d702cc413e6cffa777';
|
|
const TREASURY_ADDRESS = '0x0bb1810061c2f5b2088054ee184e6c79e1591101';
|
|
const PROPOSER = process.env.PROPOSER || constants.NULL_ADDRESS;
|
|
const VOTER = '0xba4f44e774158408e2dc6c5cb65bc995f0a89180';
|
|
const VOTER_OPERATED_POOLS = ['0x0000000000000000000000000000000000000000000000000000000000000017'];
|
|
const VOTER_2 = '0x9a4eb1101c0c053505bd71d2ffa27ed902dead85';
|
|
const VOTER_2_OPERATED_POOLS = ['0x0000000000000000000000000000000000000000000000000000000000000029'];
|
|
blockchainTests.configure({
|
|
fork: {
|
|
unlockedAccounts: [PROPOSER, VOTER, VOTER_2],
|
|
},
|
|
});
|
|
|
|
async function querySubgraphAsync(operatorAddress: string): Promise<string[]> {
|
|
const query = `
|
|
{
|
|
stakingActor(id: "${operatorAddress}") {
|
|
operatedPools {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
const response = await fetch(SUBGRAPH_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
query,
|
|
}),
|
|
});
|
|
const {
|
|
data: { stakingActor },
|
|
} = await response.json();
|
|
if (stakingActor) {
|
|
return stakingActor.operatedPools.map((pool: { id: string }) => hexUtils.leftPad(pool.id));
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
blockchainTests.fork.skip('Treasury proposal mainnet fork tests', env => {
|
|
let staking: StakingContract;
|
|
let stakingProxy: StakingProxyContract;
|
|
let treasury: ZrxTreasuryContract;
|
|
let votingPeriod: BigNumber;
|
|
|
|
async function fastForwardToNextEpochAsync(): Promise<void> {
|
|
const epochEndTime = await staking.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
|
|
const lastBlockTime = await env.web3Wrapper.getBlockTimestampAsync('latest');
|
|
const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
|
|
await env.web3Wrapper.increaseTimeAsync(dt);
|
|
// mine next block
|
|
await env.web3Wrapper.mineBlockAsync();
|
|
const lastPoolId = new BigNumber(await staking.lastPoolId().callAsync(), 16);
|
|
const batchExecuteCalldata = [
|
|
...[...new Array(lastPoolId.toNumber())].map((_x, i) =>
|
|
staking.finalizePool(hexUtils.leftPad(i + 1)).getABIEncodedTransactionData(),
|
|
),
|
|
staking.endEpoch().getABIEncodedTransactionData(),
|
|
...[...new Array(lastPoolId.toNumber())].map((_x, i) =>
|
|
staking.finalizePool(hexUtils.leftPad(i + 1)).getABIEncodedTransactionData(),
|
|
),
|
|
...[...new Array(lastPoolId.toNumber())].map((_x, i) =>
|
|
staking.finalizePool(hexUtils.leftPad(i + 1)).getABIEncodedTransactionData(),
|
|
),
|
|
];
|
|
await stakingProxy.batchExecute(batchExecuteCalldata).awaitTransactionSuccessAsync();
|
|
}
|
|
|
|
before(async () => {
|
|
const abis = _.mapValues({ ...artifacts, ...erc20Artifacts }, v => v.compilerOutput.abi);
|
|
treasury = new ZrxTreasuryContract(TREASURY_ADDRESS, env.provider, env.txDefaults, abis);
|
|
votingPeriod = await treasury.votingPeriod().callAsync();
|
|
staking = new StakingContract(STAKING_PROXY_ADDRESS, env.provider, env.txDefaults);
|
|
stakingProxy = new StakingProxyContract(STAKING_PROXY_ADDRESS, env.provider, env.txDefaults);
|
|
});
|
|
|
|
describe('Proposal 0', () => {
|
|
it('works', async () => {
|
|
const proposal = proposals[0];
|
|
let executionEpoch: BigNumber;
|
|
if (proposal.executionEpoch) {
|
|
executionEpoch = proposal.executionEpoch;
|
|
} else {
|
|
const currentEpoch = await staking.currentEpoch().callAsync();
|
|
executionEpoch = currentEpoch.plus(2);
|
|
}
|
|
const pools = await querySubgraphAsync(PROPOSER);
|
|
const proposeTx = treasury.propose(proposal.actions, executionEpoch, proposal.description, pools);
|
|
|
|
const calldata = proposeTx.getABIEncodedTransactionData();
|
|
logUtils.log('ZrxTreasury.propose calldata:');
|
|
logUtils.log(calldata);
|
|
|
|
const proposalId = await proposeTx.callAsync({ from: PROPOSER });
|
|
const receipt = await proposeTx.awaitTransactionSuccessAsync({ from: PROPOSER });
|
|
verifyEventsFromLogs(
|
|
receipt.logs,
|
|
[
|
|
{
|
|
...proposal,
|
|
proposalId,
|
|
executionEpoch,
|
|
proposer: PROPOSER,
|
|
operatedPoolIds: pools,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalCreated,
|
|
);
|
|
await fastForwardToNextEpochAsync();
|
|
await fastForwardToNextEpochAsync();
|
|
await treasury
|
|
.castVote(proposalId, true, VOTER_OPERATED_POOLS)
|
|
.awaitTransactionSuccessAsync({ from: VOTER });
|
|
await env.web3Wrapper.increaseTimeAsync(votingPeriod.plus(1).toNumber());
|
|
await env.web3Wrapper.mineBlockAsync();
|
|
const executeTx = await treasury.execute(proposalId, proposal.actions).awaitTransactionSuccessAsync();
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
proposalId,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalExecuted,
|
|
);
|
|
const recipient = '0xf9347f751a6a1467abc722ec7d80ba2698dd9d6c';
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
_from: TREASURY_ADDRESS,
|
|
_to: recipient,
|
|
_value: new BigNumber(400_000).times('1e18'),
|
|
},
|
|
],
|
|
ERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('Proposal 1', () => {
|
|
it('works', async () => {
|
|
const proposal = proposals[1];
|
|
let executionEpoch: BigNumber;
|
|
if (proposal.executionEpoch) {
|
|
executionEpoch = proposal.executionEpoch;
|
|
} else {
|
|
const currentEpoch = await staking.currentEpoch().callAsync();
|
|
executionEpoch = currentEpoch.plus(2);
|
|
}
|
|
const pools = await querySubgraphAsync(PROPOSER);
|
|
const proposeTx = treasury.propose(proposal.actions, executionEpoch, proposal.description, pools);
|
|
|
|
const calldata = proposeTx.getABIEncodedTransactionData();
|
|
logUtils.log('ZrxTreasury.propose calldata:');
|
|
logUtils.log(calldata);
|
|
|
|
const proposalId = await proposeTx.callAsync({ from: PROPOSER });
|
|
const receipt = await proposeTx.awaitTransactionSuccessAsync({ from: PROPOSER });
|
|
verifyEventsFromLogs(
|
|
receipt.logs,
|
|
[
|
|
{
|
|
...proposal,
|
|
proposalId,
|
|
executionEpoch,
|
|
proposer: PROPOSER,
|
|
operatedPoolIds: pools,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalCreated,
|
|
);
|
|
await fastForwardToNextEpochAsync();
|
|
await fastForwardToNextEpochAsync();
|
|
await treasury
|
|
.castVote(proposalId, true, VOTER_OPERATED_POOLS)
|
|
.awaitTransactionSuccessAsync({ from: VOTER });
|
|
await env.web3Wrapper.increaseTimeAsync(votingPeriod.plus(1).toNumber());
|
|
await env.web3Wrapper.mineBlockAsync();
|
|
const executeTx = await treasury.execute(proposalId, proposal.actions).awaitTransactionSuccessAsync();
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
proposalId,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalExecuted,
|
|
);
|
|
const recipient = '0xab66cc8fd10457ebc9d13b9760c835f0a4cbc487';
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
_from: TREASURY_ADDRESS,
|
|
_to: recipient,
|
|
_value: new BigNumber(330_813).times('1e18'),
|
|
},
|
|
{
|
|
_from: TREASURY_ADDRESS,
|
|
_to: recipient,
|
|
_value: new BigNumber(420000).times('1e18'),
|
|
},
|
|
],
|
|
ERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('Proposal 2', () => {
|
|
it('works', async () => {
|
|
const proposal = proposals[2];
|
|
let executionEpoch: BigNumber;
|
|
if (proposal.executionEpoch) {
|
|
executionEpoch = proposal.executionEpoch;
|
|
} else {
|
|
const currentEpoch = await staking.currentEpoch().callAsync();
|
|
executionEpoch = currentEpoch.plus(2);
|
|
}
|
|
const pools = await querySubgraphAsync(PROPOSER);
|
|
const proposeTx = treasury.propose(proposal.actions, executionEpoch, proposal.description, pools);
|
|
|
|
const calldata = proposeTx.getABIEncodedTransactionData();
|
|
logUtils.log('ZrxTreasury.propose calldata:');
|
|
logUtils.log(calldata);
|
|
|
|
const proposalId = await proposeTx.callAsync({ from: PROPOSER });
|
|
const receipt = await proposeTx.awaitTransactionSuccessAsync({ from: PROPOSER });
|
|
verifyEventsFromLogs(
|
|
receipt.logs,
|
|
[
|
|
{
|
|
...proposal,
|
|
proposalId,
|
|
executionEpoch,
|
|
proposer: PROPOSER,
|
|
operatedPoolIds: pools,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalCreated,
|
|
);
|
|
await fastForwardToNextEpochAsync();
|
|
await fastForwardToNextEpochAsync();
|
|
await treasury
|
|
.castVote(proposalId, true, VOTER_OPERATED_POOLS)
|
|
.awaitTransactionSuccessAsync({ from: VOTER });
|
|
await treasury
|
|
.castVote(proposalId, true, VOTER_2_OPERATED_POOLS)
|
|
.awaitTransactionSuccessAsync({ from: VOTER_2 });
|
|
await env.web3Wrapper.increaseTimeAsync(votingPeriod.plus(1).toNumber());
|
|
await env.web3Wrapper.mineBlockAsync();
|
|
const executeTx = await treasury.execute(proposalId, proposal.actions).awaitTransactionSuccessAsync();
|
|
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
proposalId,
|
|
},
|
|
],
|
|
ZrxTreasuryEvents.ProposalExecuted,
|
|
);
|
|
|
|
verifyEventsFromLogs(
|
|
executeTx.logs,
|
|
[
|
|
{
|
|
recipient: '0x976378445D31D81b15576811450A7b9797206807',
|
|
deposit: new BigNumber('485392999999999970448000'),
|
|
tokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
|
|
startTime: new BigNumber(1635188400),
|
|
stopTime: new BigNumber(1666724400),
|
|
},
|
|
{
|
|
recipient: '0x976378445D31D81b15576811450A7b9797206807',
|
|
deposit: new BigNumber('378035999999999992944000'),
|
|
tokenAddress: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0',
|
|
startTime: new BigNumber(1635188400),
|
|
stopTime: new BigNumber(1666724400),
|
|
},
|
|
],
|
|
ISablierEvents.CreateStream,
|
|
);
|
|
});
|
|
});
|
|
});
|