Skip to content

Commit 64a6ff5

Browse files
0xjockesunnyvempatinoahlitvinbarrassocavalier-eth
authored
Perpsv3 andromeda tests (#93)
* fee stuff * bump to 3.3.5 * release v3.3.5 fixes * add kwenta and infinex fee addresses * spacing * add newline * add setting * Add missing parameter (#90) * add missing parameter * lint * fix * update cannon * Enable local IPFS in CI * set btc FeeRatios to 2/5 * Bump version * Readme update * Burn leftover debt (#92) * Add mining, debt burning, and sUSD swapping in Ethereum end-to-end testing New functionalities are implemented to enrich the capabilities of Ethereum end-to-end testing. These include the addition of block mining, debt burning, and swapping to sUSD. The changes also extend the tasks in the Liquidity Provider e2e test, including elements such as account creation and sUSD deposit, thereby boosting the robustness and coverage of the testing script. * Add functionality to undelegate collateral * base mainnet andromeda setup (#91) * base mainnet andromeda setup * pretty * limit to $50k LP and $10k OI as updated SIP-348 * added some todos * mainnet changes * fix url * fix include order * fixes * fixes * build success * deniers * susdc override * fix * remove susdc from deduction * update parameters * update settlement delay * fixes --------- Co-authored-by: cavalier_eth <[email protected]> Co-authored-by: Noah Litvin <[email protected]> Co-authored-by: kaleb <[email protected]> * fixes missing pyth verification address * clean up * fix * update parameters (#94) * Make setting.synthUsdcMaxMarketCollateral readable again * Fix syntax issues * Correct maker and taker fee * Correct path to PerpsMarketProxyDeployment * Move out account creation from config test, and create Perps trading test file * Add deposit test * Correct casing (bad merge) * Add withdraw * Add withdrawal test * WIP trade test * Fix deposit to Perps market and separate test steps for easier copypaste * Add pyth errors and remove duplicates * Add a getPrice function - This is useful to create acceptable price when making trades * Remove testnet * Make sure we can fulfill oracle queries for UPDATE_TYPE=2 * Add open and close trade tests * Remove log and add missing import * Correct test label * yarn pretty * Settle orders with 0 settlement delay * Remove unused wait * Remove .only and use .catch * Remove try catch and use wait for settlementDelay * Remove ts-check Co-authored-by: Noisekit <[email protected]> * Unnest includes * Remove trailing commas * Unnest includes for mainnet * Exact settings values with parseEther for referrerFee * Remove try catch * More logs * Human-readable feeCollector settings * Cleanup unnecessary comments * Testnet contracts are DAO-owned now * Multiline collateral configs * remove infinex fee, confirm kwenta and poly * Multiline collateral configs * Prettier * Better start:andromeda * Extract tasks for perps collateral * Refactor with tasks * short/long acceptablePrice adjustment * Fix setSettlementDelay * Settle order and get position tasks * Another wait before committing order to close position --------- Co-authored-by: Sunny Vempati <[email protected]> Co-authored-by: Noah Litvin <[email protected]> Co-authored-by: meb <[email protected]> Co-authored-by: cavalier_eth <[email protected]> Co-authored-by: Leonardo Massazza <[email protected]> Co-authored-by: Noisekit <[email protected]> Co-authored-by: kaleb <[email protected]> Co-authored-by: kaleb <[email protected]>
1 parent 6a00de7 commit 64a6ff5

15 files changed

+667
-64
lines changed

e2e/parseError.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,50 @@ const ERC7412_ABI = [
55
'error OracleDataRequired(address oracleContract, bytes oracleQuery)',
66
'error FeeRequired(uint feeAmount)',
77
];
8+
const PYTH_ERRORS = [
9+
// Function arguments are invalid (e.g., the arguments lengths mismatch)
10+
// Signature: 0xa9cb9e0d
11+
'error InvalidArgument()',
12+
// Update data is coming from an invalid data source.
13+
// Signature: 0xe60dce71
14+
'error InvalidUpdateDataSource()',
15+
// Update data is invalid (e.g., deserialization error)
16+
// Signature: 0xe69ffece
17+
'error InvalidUpdateData()',
18+
// Insufficient fee is paid to the method.
19+
// Signature: 0x025dbdd4
20+
'error InsufficientFee()',
21+
// There is no fresh update, whereas expected fresh updates.
22+
// Signature: 0xde2c57fa
23+
'error NoFreshUpdate()',
24+
// There is no price feed found within the given range or it does not exists.
25+
// Signature: 0x45805f5d
26+
'error PriceFeedNotFoundWithinRange()',
27+
// Price feed not found or it is not pushed on-chain yet.
28+
// Signature: 0x14aebe68
29+
'error PriceFeedNotFound()',
30+
// Requested price is stale.
31+
// Signature: 0x19abf40e
32+
'error StalePrice()',
33+
// Given message is not a valid Wormhole VAA.
34+
// Signature: 0x2acbe915
35+
'error InvalidWormholeVaa()',
36+
// Governance message is invalid (e.g., deserialization error).
37+
// Signature: 0x97363b35
38+
'error InvalidGovernanceMessage()',
39+
// Governance message is not for this contract.
40+
// Signature: 0x63daeb77
41+
'error InvalidGovernanceTarget()',
42+
// Governance message is coming from an invalid data source.
43+
// Signature: 0x360f2d87
44+
'error InvalidGovernanceDataSource()',
45+
// Governance message is old.
46+
// Signature: 0x88d1b847
47+
'error OldGovernanceMessage()',
48+
// The wormhole address to set in SetWormholeAddress governance is invalid.
49+
// Signature: 0x13d3ed82
50+
'error InvalidWormholeAddressToSet()',
51+
];
852

953
function parseError(error) {
1054
const rpcError = error?.error?.error?.error;
@@ -18,7 +62,11 @@ function parseError(error) {
1862
const errorParsed = (() => {
1963
try {
2064
const { abi, address } = require('./deployments/AllErrors.json');
21-
const AllErrors = new ethers.Contract(address, abi.concat(ERC7412_ABI), provider);
65+
const AllErrors = new ethers.Contract(
66+
address,
67+
[...new Set(abi.concat(ERC7412_ABI).concat(PYTH_ERRORS))],
68+
provider
69+
);
2270
const data = AllErrors.interface.parseError(errorData);
2371
return data;
2472
} catch (e) {}

e2e/tasks/commitPerpsOrder.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const { ethers } = require('ethers');
2+
const { getPythPrice } = require('./getPythPrice');
3+
const { getPerpsSettlementStrategy } = require('./getPerpsSettlementStrategy');
4+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
5+
const { parseError } = require('../parseError');
6+
7+
const log = require('debug')(`e2e:${require('path').basename(__filename, '.js')}`);
8+
9+
async function commitPerpsOrder({ wallet, accountId, marketId, sizeDelta, settlementStrategyId }) {
10+
log({ address: wallet.address, accountId, marketId, sizeDelta, settlementStrategyId });
11+
12+
const provider = new ethers.providers.JsonRpcProvider(
13+
process.env.RPC_URL || 'http://127.0.0.1:8545'
14+
);
15+
16+
const PerpsMarketProxy = new ethers.Contract(
17+
PerpsMarketProxyDeployment.address,
18+
PerpsMarketProxyDeployment.abi,
19+
wallet
20+
);
21+
22+
const { feedId } = await getPerpsSettlementStrategy({ marketId, settlementStrategyId });
23+
log({ feedId });
24+
25+
const pythPrice = await getPythPrice({ feedId });
26+
log({ pythPrice });
27+
28+
const tx = await PerpsMarketProxy.commitOrder(
29+
{
30+
marketId,
31+
accountId,
32+
sizeDelta: ethers.utils.parseEther(`${sizeDelta}`),
33+
settlementStrategyId,
34+
acceptablePrice: ethers.utils.parseEther(
35+
Math.floor(pythPrice * (sizeDelta > 0 ? 2 : 0.5)).toString()
36+
),
37+
referrer: ethers.constants.AddressZero,
38+
trackingCode: ethers.constants.HashZero,
39+
},
40+
{ gasLimit: 10_000_000 }
41+
).catch(parseError);
42+
await tx.wait().catch(parseError);
43+
44+
const commitReceipt = await tx.wait().catch(parseError);
45+
const block = await provider.getBlock(commitReceipt.blockNumber);
46+
const commitmentTime = block.timestamp;
47+
log({ commitmentTime });
48+
49+
return { commitmentTime };
50+
}
51+
52+
module.exports = {
53+
commitPerpsOrder,
54+
};

e2e/tasks/createPerpsAccount.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { ethers } = require('ethers');
22
// const crypto = require('crypto');
33
const { getPerpsAccountOwner } = require('./getPerpsAccountOwner');
4-
const PerpsMarketProxyDeployment = require('../deployments/CoreProxy.json');
4+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
55
const { parseError } = require('../parseError');
66

77
const log = require('debug')(`e2e:${require('path').basename(__filename, '.js')}`);

e2e/tasks/fulfillOracleQuery.js

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,69 @@
22

33
const { ethers } = require('ethers');
44
const { EvmPriceServiceConnection } = require('@pythnetwork/pyth-evm-js');
5-
const oracles = require('../deployments/oracles.json');
5+
const { parseError } = require('../parseError');
6+
const { getPerpsSettlementStrategy } = require('./getPerpsSettlementStrategy');
67

78
const log = require('debug')(`e2e:${require('path').basename(__filename, '.js')}`);
89

9-
const PYTH_MAINNET_ENDPOINT = 'https://xc-mainnet.pyth.network';
10-
const PYTH_TESTNET_ENDPOINT = 'https://xc-testnet.pyth.network';
10+
const PYTH_MAINNET_ENDPOINT = 'https://hermes.pyth.network';
1111

1212
const ERC7412_ABI = [
1313
'error OracleDataRequired(address oracleContract, bytes oracleQuery)',
1414
'error FeeRequired(uint feeAmount)',
1515
'function oracleId() view external returns (bytes32)',
1616
'function fulfillOracleQuery(bytes calldata signedOffchainData) payable external',
1717
];
18+
const priceService = new EvmPriceServiceConnection(PYTH_MAINNET_ENDPOINT);
1819

19-
async function fulfillOracleQuery({ wallet, symbol, isTestnet }) {
20-
if (!(symbol in oracles)) {
21-
throw new Error(`Symbol ${symbol} not found in oracles`);
22-
}
23-
24-
const priceService = new EvmPriceServiceConnection(
25-
isTestnet ? PYTH_TESTNET_ENDPOINT : PYTH_MAINNET_ENDPOINT
26-
);
20+
function base64ToHex(str) {
21+
const raw = Buffer.from(str, 'base64');
22+
return '0x' + raw.toString('hex');
23+
}
2724

28-
log({ symbol });
25+
async function fulfillOracleQuery({ wallet, marketId, settlementStrategyId, commitmentTime }) {
26+
const { feedId, priceVerificationContract, commitmentPriceDelay } =
27+
await getPerpsSettlementStrategy({ marketId, settlementStrategyId });
2928

30-
const oracle = oracles[symbol];
29+
log({ feedId, priceVerificationContract, commitmentPriceDelay });
3130

32-
const [offchainData] = await priceService.getPriceFeedsUpdateData([oracle.feedId]);
31+
const timestamp = commitmentTime + commitmentPriceDelay.toNumber();
32+
const [offchainData] = await priceService.getVaa(feedId, timestamp);
33+
const UPDATE_TYPE = 2;
3334
const offchainDataEncoded = ethers.utils.defaultAbiCoder.encode(
3435
['uint8', 'uint64', 'bytes32[]', 'bytes[]'],
35-
[1, oracle.staleness, [oracle.feedId], [offchainData]]
36+
[UPDATE_TYPE, timestamp, [feedId], [base64ToHex(offchainData)]]
3637
);
37-
const OracleBTC = new ethers.Contract(oracle.address, ERC7412_ABI, wallet);
3838

39-
const tx = await OracleBTC.fulfillOracleQuery(offchainDataEncoded, {
39+
const PriceVerificationContract = new ethers.Contract(
40+
priceVerificationContract,
41+
ERC7412_ABI,
42+
wallet
43+
);
44+
45+
const tx = await PriceVerificationContract.fulfillOracleQuery(offchainDataEncoded, {
4046
value: ethers.BigNumber.from(1), // 1 wei,
41-
gasLimit: 10_000_000,
42-
});
43-
await tx.wait();
44-
log({ symbol, updated: true });
47+
}).catch(parseError);
48+
await tx.wait().catch(parseError);
49+
50+
log({ feedId, updated: true });
4551
}
4652

4753
module.exports = {
4854
fulfillOracleQuery,
4955
};
5056

5157
if (require.main === module) {
52-
const [pk, testnet, symbol] = process.argv.slice(2);
53-
if (!pk || !testnet || !symbol) {
58+
const [pk, marketId, settlementStrategyId] = process.argv.slice(2);
59+
if (!pk || !marketId || !settlementStrategyId) {
5460
const bin = `./${require('path').basename(__filename)}`;
5561
throw new Error(
5662
[
63+
//
5764
'Usage:',
58-
` ${bin} pk testnet symbol`,
59-
'Example:',
60-
` ${bin} 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 testnet BTC`,
65+
` ${bin} pk marketId settlementStrategyId`,
66+
'Example (for BTC):',
67+
` ${bin} pk 200 0`,
6168
'',
6269
].join('\n')
6370
);
@@ -66,5 +73,10 @@ if (require.main === module) {
6673
process.env.RPC_URL || 'http://127.0.0.1:8545'
6774
);
6875
const wallet = new ethers.Wallet(pk, provider);
69-
fulfillOracleQuery({ wallet, symbol, isTestnet: testnet === 'testnet' }).then(console.log);
76+
fulfillOracleQuery({
77+
wallet,
78+
marketId,
79+
settlementStrategyId,
80+
commitmentTime: Date.now(),
81+
}).then(console.log);
7082
}

e2e/tasks/getPerpsAccountOwner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { ethers } = require('ethers');
2-
const PerpsMarketProxyDeployment = require('../deployments/CoreProxy.json');
2+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
33

44
async function getPerpsAccountOwner({ accountId }) {
55
const provider = new ethers.providers.JsonRpcProvider(

e2e/tasks/getPerpsCollateral.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const { ethers } = require('ethers');
2+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
3+
4+
async function getPerpsCollateral({ accountId }) {
5+
const sUSDMarketId = 0;
6+
7+
const provider = new ethers.providers.JsonRpcProvider(
8+
process.env.RPC_URL || 'http://127.0.0.1:8545'
9+
);
10+
const PerpsMarketProxy = new ethers.Contract(
11+
PerpsMarketProxyDeployment.address,
12+
PerpsMarketProxyDeployment.abi,
13+
provider
14+
);
15+
16+
return parseFloat(
17+
ethers.utils.formatUnits(await PerpsMarketProxy.getCollateralAmount(accountId, sUSDMarketId))
18+
);
19+
}
20+
21+
module.exports = {
22+
getPerpsCollateral,
23+
};
24+
25+
if (require.main === module) {
26+
const [accountId] = process.argv.slice(2);
27+
getPerpsCollateral({ accountId }).then(console.log);
28+
}

e2e/tasks/getPerpsPosition.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const { ethers } = require('ethers');
2+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
3+
4+
async function getPerpsPosition({ accountId, marketId }) {
5+
const provider = new ethers.providers.JsonRpcProvider(
6+
process.env.RPC_URL || 'http://127.0.0.1:8545'
7+
);
8+
const PerpsMarketProxy = new ethers.Contract(
9+
PerpsMarketProxyDeployment.address,
10+
PerpsMarketProxyDeployment.abi,
11+
provider
12+
);
13+
14+
const [totalPnl, accruedFunding, positionSize] = await PerpsMarketProxy.getOpenPosition(
15+
accountId,
16+
marketId
17+
);
18+
19+
return {
20+
totalPnl: parseFloat(ethers.utils.formatUnits(totalPnl)),
21+
accruedFunding: parseFloat(ethers.utils.formatUnits(accruedFunding)),
22+
positionSize: parseFloat(ethers.utils.formatUnits(positionSize)),
23+
};
24+
}
25+
26+
module.exports = {
27+
getPerpsPosition,
28+
};
29+
30+
if (require.main === module) {
31+
const [accountId, marketId] = process.argv.slice(2);
32+
getPerpsPosition({ accountId, marketId }).then(console.log);
33+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const { ethers } = require('ethers');
2+
const PerpsMarketProxyDeployment = require('../deployments/PerpsMarketProxy.json');
3+
4+
async function getPerpsSettlementStrategy({ marketId, settlementStrategyId }) {
5+
const provider = new ethers.providers.JsonRpcProvider(
6+
process.env.RPC_URL || 'http://127.0.0.1:8545'
7+
);
8+
9+
const PerpsMarketProxy = new ethers.Contract(
10+
PerpsMarketProxyDeployment.address,
11+
PerpsMarketProxyDeployment.abi,
12+
provider
13+
);
14+
15+
const [
16+
strategyType,
17+
settlementDelay,
18+
settlementWindowDuration,
19+
priceVerificationContract,
20+
feedId,
21+
settlementReward,
22+
disabled,
23+
commitmentPriceDelay,
24+
] = await PerpsMarketProxy.getSettlementStrategy(marketId, settlementStrategyId);
25+
26+
return {
27+
strategyType,
28+
settlementDelay,
29+
settlementWindowDuration,
30+
priceVerificationContract,
31+
feedId,
32+
settlementReward,
33+
disabled,
34+
commitmentPriceDelay,
35+
};
36+
}
37+
38+
module.exports = {
39+
getPerpsSettlementStrategy,
40+
};
41+
42+
if (require.main === module) {
43+
const [marketId, settlementStrategyId] = process.argv.slice(2);
44+
getPerpsSettlementStrategy({ marketId, settlementStrategyId }).then(console.log);
45+
}

e2e/tasks/getPythPrice.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
3+
const { EvmPriceServiceConnection } = require('@pythnetwork/pyth-evm-js');
4+
5+
const log = require('debug')(`e2e:${require('path').basename(__filename, '.js')}`);
6+
7+
async function getPythPrice({ feedId }) {
8+
const PYTH_MAINNET_ENDPOINT = 'https://hermes.pyth.network';
9+
const priceService = new EvmPriceServiceConnection(PYTH_MAINNET_ENDPOINT);
10+
const feeds = await priceService.getLatestPriceFeeds([feedId]);
11+
log({ feedId });
12+
if (!feeds || feeds.length !== 1) {
13+
throw Error(`Price feed not found, feed id: ${feedId}`);
14+
}
15+
const [feed] = feeds;
16+
const uncheckedPrice = feed.getPriceUnchecked();
17+
const price = uncheckedPrice.getPriceAsNumberUnchecked();
18+
log({ feedId, price });
19+
return price;
20+
}
21+
22+
module.exports = {
23+
getPythPrice,
24+
};

0 commit comments

Comments
 (0)