mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-30 03:32:07 +00:00
uni-watcher smoke test (#172)
* Created a smoke test for uni-watcher. * Added test for createPool. * Added more pool tests. * Using ethers instead of hardhat-ethers in smoke test. Co-authored-by: prathamesh0 <prathamesh.musale0@gmail.com>
This commit is contained in:
parent
18ebed0b36
commit
9a3ac28a5d
@ -34,5 +34,5 @@
|
||||
deleteOnStart = false
|
||||
|
||||
[jobQueue]
|
||||
dbConnectionString = "postgres://postgres:postgres@localhost/job-queue"
|
||||
dbConnectionString = "postgres://postgres:postgres@localhost/address-watcher-job-queue"
|
||||
maxCompletionLag = 300
|
||||
|
@ -23,5 +23,14 @@
|
||||
"allowArgumentsExplicitlyTypedAsAny": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.ts", "test/*.ts"],
|
||||
"rules": {
|
||||
"no-unused-expressions": "off",
|
||||
"no-prototype-builtins":"off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
6
packages/uni-watcher/.gitignore
vendored
6
packages/uni-watcher/.gitignore
vendored
@ -3,4 +3,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
tmp/
|
||||
temp/
|
||||
temp/
|
||||
|
||||
#Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
|
6
packages/uni-watcher/.mocharc.json
Normal file
6
packages/uni-watcher/.mocharc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"timeout": "30000",
|
||||
"bail": true,
|
||||
"exit": true,
|
||||
"require": "ts-node/register"
|
||||
}
|
@ -86,3 +86,14 @@ To test the watchers locally:
|
||||
* Send transactions to trigger events
|
||||
|
||||
See https://github.com/vulcanize/uniswap-v3-periphery/blob/watcher-ts/demo.md for instructions.
|
||||
|
||||
### Smoke test
|
||||
|
||||
To run a smoke test:
|
||||
|
||||
* Start the server and the job-runner.
|
||||
* Run:
|
||||
|
||||
```bash
|
||||
$ yarn smoke-test
|
||||
```
|
||||
|
21
packages/uni-watcher/hardhat.config.ts
Normal file
21
packages/uni-watcher/hardhat.config.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { HardhatUserConfig } from 'hardhat/config';
|
||||
import '@nomiclabs/hardhat-waffle';
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
defaultNetwork: 'localhost',
|
||||
solidity: {
|
||||
compilers: [
|
||||
{
|
||||
version: '0.7.6'
|
||||
},
|
||||
{
|
||||
version: '0.5.0'
|
||||
}
|
||||
]
|
||||
},
|
||||
paths: {
|
||||
sources: './test/contracts'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
@ -9,10 +9,12 @@
|
||||
"server:mock": "MOCK=1 nodemon src/server.ts -f environments/local.toml",
|
||||
"job-runner": "DEBUG=vulcanize:* nodemon src/job-runner.ts -f environments/local.toml",
|
||||
"fill": "DEBUG=vulcanize:* ts-node src/fill.ts -f environments/local.toml",
|
||||
"test": "mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"test": "mocha src/**/*.spec.ts",
|
||||
"lint": "eslint .",
|
||||
"build": "tsc",
|
||||
"watch:contract": "ts-node src/cli/watch-contract.ts --configFile environments/local.toml"
|
||||
"watch:contract": "ts-node src/cli/watch-contract.ts --configFile environments/local.toml",
|
||||
"test:compile": "hardhat compile",
|
||||
"smoke-test": "yarn test:compile && mocha src/smoke.test.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -52,12 +54,18 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/abi": "^5.3.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/json-bigint": "^1.0.0",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.25.0",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"chai": "^4.3.4",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-semistandard": "^15.0.1",
|
||||
@ -66,7 +74,10 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"hardhat": "^2.3.0",
|
||||
"mocha": "^8.4.0",
|
||||
"nodemon": "^2.0.7"
|
||||
"nodemon": "^2.0.7",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import assert from 'assert';
|
||||
import yargs from 'yargs';
|
||||
import 'reflect-metadata';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import { Config, getConfig } from '@vulcanize/util';
|
||||
|
||||
import { Database } from '../database';
|
||||
import { watchContract } from '../utils/index';
|
||||
|
||||
(async () => {
|
||||
const argv = await yargs.parserConfiguration({
|
||||
@ -43,9 +44,7 @@ import { Database } from '../database';
|
||||
const db = new Database(dbConfig);
|
||||
await db.init();
|
||||
|
||||
// Always use the checksum address (https://docs.ethers.io/v5/api/utils/address/#utils-getAddress).
|
||||
const address = ethers.utils.getAddress(argv.address);
|
||||
await watchContract(db, argv.address, argv.kind, argv.startingBlock);
|
||||
|
||||
await db.saveContract(address, argv.kind, argv.startingBlock);
|
||||
await db.close();
|
||||
})();
|
||||
|
268
packages/uni-watcher/src/smoke.test.ts
Normal file
268
packages/uni-watcher/src/smoke.test.ts
Normal file
@ -0,0 +1,268 @@
|
||||
import { expect, assert } from 'chai';
|
||||
import { ethers, Contract, Signer } from 'ethers';
|
||||
import 'reflect-metadata';
|
||||
import 'mocha';
|
||||
|
||||
import { Config, getConfig } from '@vulcanize/util';
|
||||
import { Client as UniClient } from '@vulcanize/uni-watcher';
|
||||
import { getCache } from '@vulcanize/cache';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import {
|
||||
abi as FACTORY_ABI,
|
||||
bytecode as FACTORY_BYTECODE
|
||||
} from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json';
|
||||
import {
|
||||
abi as POOL_ABI
|
||||
} from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json';
|
||||
|
||||
import { Indexer } from './indexer';
|
||||
import { Database } from './database';
|
||||
import { watchContract } from './utils/index';
|
||||
import { testCreatePool, testInitialize } from '../test/utils';
|
||||
import {
|
||||
abi as TESTERC20_ABI,
|
||||
bytecode as TESTERC20_BYTECODE
|
||||
} from '../artifacts/test/contracts/TestERC20.sol/TestERC20.json';
|
||||
import {
|
||||
abi as TESTUNISWAPV3CALLEE_ABI,
|
||||
bytecode as TESTUNISWAPV3CALLEE_BYTECODE
|
||||
} from '../artifacts/test/contracts/TestUniswapV3Callee.sol/TestUniswapV3Callee.json';
|
||||
|
||||
const TICK_MIN = -887272;
|
||||
const TICK_MAX = 887272;
|
||||
const getMinTick = (tickSpacing: number) => Math.ceil(TICK_MIN / tickSpacing) * tickSpacing;
|
||||
const getMaxTick = (tickSpacing: number) => Math.floor(TICK_MAX / tickSpacing) * tickSpacing;
|
||||
|
||||
describe('uni-watcher', () => {
|
||||
let factory: Contract;
|
||||
let token0: Contract;
|
||||
let token1: Contract;
|
||||
let pool: Contract;
|
||||
|
||||
let poolAddress: string;
|
||||
let tickLower: number;
|
||||
let tickUpper: number;
|
||||
let config: Config;
|
||||
let db: Database;
|
||||
let uniClient: UniClient;
|
||||
let ethClient: EthClient;
|
||||
let signer: Signer;
|
||||
let recipient: string;
|
||||
|
||||
before(async () => {
|
||||
const configFile = './environments/local.toml';
|
||||
config = await getConfig(configFile);
|
||||
|
||||
const { database: dbConfig, upstream, server: { host, port } } = config;
|
||||
assert(dbConfig, 'Missing dbConfig.');
|
||||
assert(upstream, 'Missing upstream.');
|
||||
assert(host, 'Missing host.');
|
||||
assert(port, 'Missing port.');
|
||||
|
||||
const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint }, cache: cacheConfig } = upstream;
|
||||
assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint.');
|
||||
assert(gqlPostgraphileEndpoint, 'Missing upstream ethServer.gqlPostgraphileEndpoint.');
|
||||
assert(cacheConfig, 'Missing dbConfig.');
|
||||
|
||||
db = new Database(dbConfig);
|
||||
await db.init();
|
||||
|
||||
const cache = await getCache(cacheConfig);
|
||||
ethClient = new EthClient({
|
||||
gqlEndpoint: gqlApiEndpoint,
|
||||
gqlSubscriptionEndpoint: gqlPostgraphileEndpoint,
|
||||
cache
|
||||
});
|
||||
|
||||
const endpoint = `http://${host}:${port}/graphql`;
|
||||
const gqlEndpoint = endpoint;
|
||||
const gqlSubscriptionEndpoint = endpoint;
|
||||
uniClient = new UniClient({
|
||||
gqlEndpoint,
|
||||
gqlSubscriptionEndpoint
|
||||
});
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
|
||||
signer = provider.getSigner();
|
||||
recipient = await signer.getAddress();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
it('should deploy contract factory', async () => {
|
||||
// Deploy factory from uniswap package.
|
||||
const Factory = new ethers.ContractFactory(FACTORY_ABI, FACTORY_BYTECODE, signer);
|
||||
factory = await Factory.deploy();
|
||||
|
||||
expect(factory.address).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('should watch factory contract', async () => {
|
||||
// Watch factory contract.
|
||||
await watchContract(db, factory.address, 'factory', 100);
|
||||
|
||||
// Verifying with the db.
|
||||
const indexer = new Indexer(config, db, ethClient);
|
||||
assert(await indexer.isUniswapContract(factory.address), 'Factory contract not added to database.');
|
||||
});
|
||||
|
||||
it('should deploy 2 tokens', async () => {
|
||||
// Deploy 2 tokens.
|
||||
const Token = new ethers.ContractFactory(TESTERC20_ABI, TESTERC20_BYTECODE, signer);
|
||||
|
||||
token0 = await Token.deploy(ethers.BigNumber.from(2).pow(255));
|
||||
expect(token0.address).to.not.be.empty;
|
||||
|
||||
token1 = await Token.deploy(ethers.BigNumber.from(2).pow(255));
|
||||
expect(token1.address).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('should create pool', async () => {
|
||||
const fee = 500;
|
||||
|
||||
pool = await testCreatePool(uniClient, factory, token0, token1, POOL_ABI, signer, fee);
|
||||
poolAddress = pool.address;
|
||||
});
|
||||
|
||||
it('should initialize pool', async () => {
|
||||
const sqrtPrice = '4295128939';
|
||||
|
||||
await testInitialize(uniClient, pool, TICK_MIN, sqrtPrice);
|
||||
});
|
||||
|
||||
it('should mint specified amount', done => {
|
||||
(async () => {
|
||||
const amount = '10';
|
||||
const approveAmount = BigInt(1000000000000000000000000);
|
||||
|
||||
const TestUniswapV3Callee = new ethers.ContractFactory(TESTUNISWAPV3CALLEE_ABI, TESTUNISWAPV3CALLEE_BYTECODE, signer);
|
||||
const poolCallee = await TestUniswapV3Callee.deploy();
|
||||
|
||||
const tickSpacing = await pool.tickSpacing();
|
||||
// https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196
|
||||
tickLower = getMinTick(tickSpacing);
|
||||
tickUpper = getMaxTick(tickSpacing);
|
||||
|
||||
// Approving tokens for TestUniswapV3Callee contract.
|
||||
// https://github.com/Uniswap/uniswap-v3-core/blob/main/test/shared/utilities.ts#L187
|
||||
const t0 = await token0.approve(poolCallee.address, approveAmount);
|
||||
await t0.wait();
|
||||
|
||||
const t1 = await token1.approve(poolCallee.address, approveAmount);
|
||||
await t1.wait();
|
||||
|
||||
// Subscribe using UniClient.
|
||||
const subscription = await uniClient.watchEvents((value: any) => {
|
||||
if (value.event.__typename === 'MintEvent') {
|
||||
expect(value.block).to.not.be.empty;
|
||||
expect(value.tx).to.not.be.empty;
|
||||
expect(value.contract).to.equal(pool.address);
|
||||
expect(value.eventIndex).to.be.a('number');
|
||||
|
||||
expect(value.event.__typename).to.equal('MintEvent');
|
||||
expect(value.event.sender).to.equal(poolCallee.address);
|
||||
expect(value.event.owner).to.equal(recipient);
|
||||
expect(value.event.tickLower).to.equal(tickLower.toString());
|
||||
expect(value.event.tickUpper).to.equal(tickUpper.toString());
|
||||
expect(value.event.amount).to.equal(amount);
|
||||
expect(value.event.amount0).to.not.be.empty;
|
||||
expect(value.event.amount1).to.not.be.empty;
|
||||
|
||||
expect(value.proof).to.not.be.empty;
|
||||
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
// Pool mint.
|
||||
await poolCallee.mint(pool.address, recipient, BigInt(tickLower), BigInt(tickUpper), BigInt(amount));
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
it('should burn specified amount', done => {
|
||||
(async () => {
|
||||
const amount = '10';
|
||||
|
||||
const tickSpacing = await pool.tickSpacing();
|
||||
// https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196
|
||||
const tickLower = getMinTick(tickSpacing);
|
||||
const tickUpper = getMaxTick(tickSpacing);
|
||||
|
||||
// Subscribe using UniClient.
|
||||
const subscription = await uniClient.watchEvents((value: any) => {
|
||||
if (value.event.__typename === 'BurnEvent') {
|
||||
expect(value.block).to.not.be.empty;
|
||||
expect(value.tx).to.not.be.empty;
|
||||
expect(value.contract).to.equal(pool.address);
|
||||
expect(value.eventIndex).to.be.a('number');
|
||||
|
||||
expect(value.event.__typename).to.equal('BurnEvent');
|
||||
expect(value.event.owner).to.equal(recipient);
|
||||
expect(value.event.tickLower).to.equal(tickLower.toString());
|
||||
expect(value.event.tickUpper).to.equal(tickUpper.toString());
|
||||
expect(value.event.amount).to.equal(amount);
|
||||
expect(value.event.amount0).to.not.be.empty;
|
||||
expect(value.event.amount1).to.not.be.empty;
|
||||
|
||||
expect(value.proof).to.not.be.empty;
|
||||
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
// Pool burn.
|
||||
await pool.burn(BigInt(tickLower), BigInt(tickUpper), BigInt(amount));
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
it('should swap pool tokens', done => {
|
||||
(async () => {
|
||||
const sqrtPrice = '4295128938';
|
||||
|
||||
const TestUniswapV3Callee = new ethers.ContractFactory(TESTUNISWAPV3CALLEE_ABI, TESTUNISWAPV3CALLEE_BYTECODE, signer);
|
||||
const poolCallee = await TestUniswapV3Callee.deploy();
|
||||
|
||||
// Subscribe using UniClient.
|
||||
const subscription = await uniClient.watchEvents((value: any) => {
|
||||
if (value.event.__typename === 'SwapEvent') {
|
||||
expect(value.block).to.not.be.empty;
|
||||
expect(value.tx).to.not.be.empty;
|
||||
expect(value.contract).to.equal(poolAddress);
|
||||
expect(value.eventIndex).to.be.a('number');
|
||||
|
||||
expect(value.event.__typename).to.equal('SwapEvent');
|
||||
expect(value.event.sender).to.equal(poolCallee.address);
|
||||
expect(value.event.recipient).to.equal(recipient);
|
||||
expect(value.event.amount0).to.not.be.empty;
|
||||
expect(value.event.amount1).to.not.be.empty;
|
||||
expect(value.event.sqrtPriceX96).to.equal(sqrtPrice);
|
||||
expect(value.event.liquidity).to.not.be.empty;
|
||||
expect(value.event.tick).to.equal(TICK_MIN.toString());
|
||||
|
||||
expect(value.proof).to.not.be.empty;
|
||||
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
await poolCallee.swapToLowerSqrtPrice(poolAddress, BigInt(sqrtPrice), recipient);
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
});
|
10
packages/uni-watcher/src/utils/index.ts
Normal file
10
packages/uni-watcher/src/utils/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import { Database } from '../database';
|
||||
|
||||
export async function watchContract (db: Database, address: string, kind: string, startingBlock: number): Promise<void> {
|
||||
// Always use the checksum address (https://docs.ethers.io/v5/api/utils/address/#utils-getAddress).
|
||||
const contractAddress = ethers.utils.getAddress(address);
|
||||
|
||||
await db.saveContract(contractAddress, kind, startingBlock);
|
||||
}
|
60
packages/uni-watcher/test/contracts/TestERC20.sol
Normal file
60
packages/uni-watcher/test/contracts/TestERC20.sol
Normal file
@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.7.6;
|
||||
|
||||
import '@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol';
|
||||
|
||||
contract TestERC20 is IERC20Minimal {
|
||||
mapping(address => uint256) public override balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public override allowance;
|
||||
|
||||
constructor(uint256 amountToMint) {
|
||||
mint(msg.sender, amountToMint);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
uint256 balanceNext = balanceOf[to] + amount;
|
||||
require(balanceNext >= amount, 'overflow balance');
|
||||
balanceOf[to] = balanceNext;
|
||||
}
|
||||
|
||||
function transfer(address recipient, uint256 amount) external override returns (bool) {
|
||||
uint256 balanceBefore = balanceOf[msg.sender];
|
||||
require(balanceBefore >= amount, 'insufficient balance');
|
||||
balanceOf[msg.sender] = balanceBefore - amount;
|
||||
|
||||
uint256 balanceRecipient = balanceOf[recipient];
|
||||
require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow');
|
||||
balanceOf[recipient] = balanceRecipient + amount;
|
||||
|
||||
emit Transfer(msg.sender, recipient, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function approve(address spender, uint256 amount) external override returns (bool) {
|
||||
allowance[msg.sender][spender] = amount;
|
||||
emit Approval(msg.sender, spender, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transferFrom(
|
||||
address sender,
|
||||
address recipient,
|
||||
uint256 amount
|
||||
) external override returns (bool) {
|
||||
uint256 allowanceBefore = allowance[sender][msg.sender];
|
||||
|
||||
require(allowanceBefore >= amount, 'allowance insufficient');
|
||||
|
||||
allowance[sender][msg.sender] = allowanceBefore - amount;
|
||||
|
||||
uint256 balanceRecipient = balanceOf[recipient];
|
||||
require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient');
|
||||
balanceOf[recipient] = balanceRecipient + amount;
|
||||
uint256 balanceSender = balanceOf[sender];
|
||||
require(balanceSender >= amount, 'underflow balance sender');
|
||||
balanceOf[sender] = balanceSender - amount;
|
||||
|
||||
emit Transfer(sender, recipient, amount);
|
||||
return true;
|
||||
}
|
||||
}
|
142
packages/uni-watcher/test/contracts/TestUniswapV3Callee.sol
Normal file
142
packages/uni-watcher/test/contracts/TestUniswapV3Callee.sol
Normal file
@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.7.6;
|
||||
|
||||
import '@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol';
|
||||
|
||||
import '@uniswap/v3-core/contracts/libraries/SafeCast.sol';
|
||||
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
|
||||
|
||||
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol';
|
||||
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
|
||||
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol';
|
||||
|
||||
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
|
||||
|
||||
contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback {
|
||||
using SafeCast for uint256;
|
||||
|
||||
function swapExact0For1(
|
||||
address pool,
|
||||
uint256 amount0In,
|
||||
address recipient,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
function swap0ForExact1(
|
||||
address pool,
|
||||
uint256 amount1Out,
|
||||
address recipient,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
function swapExact1For0(
|
||||
address pool,
|
||||
uint256 amount1In,
|
||||
address recipient,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
function swap1ForExact0(
|
||||
address pool,
|
||||
uint256 amount0Out,
|
||||
address recipient,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
function swapToLowerSqrtPrice(
|
||||
address pool,
|
||||
uint160 sqrtPriceX96,
|
||||
address recipient
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
function swapToHigherSqrtPrice(
|
||||
address pool,
|
||||
uint160 sqrtPriceX96,
|
||||
address recipient
|
||||
) external {
|
||||
IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
event SwapCallback(int256 amount0Delta, int256 amount1Delta);
|
||||
|
||||
function uniswapV3SwapCallback(
|
||||
int256 amount0Delta,
|
||||
int256 amount1Delta,
|
||||
bytes calldata data
|
||||
) external override {
|
||||
address sender = abi.decode(data, (address));
|
||||
|
||||
emit SwapCallback(amount0Delta, amount1Delta);
|
||||
|
||||
if (amount0Delta > 0) {
|
||||
IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta));
|
||||
} else if (amount1Delta > 0) {
|
||||
IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta));
|
||||
} else {
|
||||
// if both are not gt 0, both must be 0.
|
||||
assert(amount0Delta == 0 && amount1Delta == 0);
|
||||
}
|
||||
}
|
||||
|
||||
function mint(
|
||||
address pool,
|
||||
address recipient,
|
||||
int24 tickLower,
|
||||
int24 tickUpper,
|
||||
uint128 amount
|
||||
) external {
|
||||
IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender));
|
||||
}
|
||||
|
||||
event MintCallback(uint256 amount0Owed, uint256 amount1Owed);
|
||||
|
||||
function uniswapV3MintCallback(
|
||||
uint256 amount0Owed,
|
||||
uint256 amount1Owed,
|
||||
bytes calldata data
|
||||
) external override {
|
||||
address sender = abi.decode(data, (address));
|
||||
|
||||
emit MintCallback(amount0Owed, amount1Owed);
|
||||
if (amount0Owed > 0)
|
||||
IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed);
|
||||
if (amount1Owed > 0)
|
||||
IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed);
|
||||
}
|
||||
|
||||
event FlashCallback(uint256 fee0, uint256 fee1);
|
||||
|
||||
function flash(
|
||||
address pool,
|
||||
address recipient,
|
||||
uint256 amount0,
|
||||
uint256 amount1,
|
||||
uint256 pay0,
|
||||
uint256 pay1
|
||||
) external {
|
||||
IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1));
|
||||
}
|
||||
|
||||
function uniswapV3FlashCallback(
|
||||
uint256 fee0,
|
||||
uint256 fee1,
|
||||
bytes calldata data
|
||||
) external override {
|
||||
emit FlashCallback(fee0, fee1);
|
||||
|
||||
(address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256));
|
||||
|
||||
if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0);
|
||||
if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1);
|
||||
}
|
||||
}
|
119
packages/uni-watcher/test/utils.ts
Normal file
119
packages/uni-watcher/test/utils.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { ethers, utils, Contract, Signer, BigNumber } from 'ethers';
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { Client as UniClient } from '@vulcanize/uni-watcher';
|
||||
|
||||
// https://github.com/ethers-io/ethers.js/issues/195
|
||||
export function linkLibraries (
|
||||
{
|
||||
bytecode,
|
||||
linkReferences
|
||||
}: {
|
||||
bytecode: string
|
||||
linkReferences: { [fileName: string]: { [contractName: string]: { length: number; start: number }[] } }
|
||||
},
|
||||
libraries: { [libraryName: string]: string }): string {
|
||||
Object.keys(linkReferences).forEach((fileName) => {
|
||||
Object.keys(linkReferences[fileName]).forEach((contractName) => {
|
||||
if (!libraries.hasOwnProperty(contractName)) {
|
||||
throw new Error(`Missing link library name ${contractName}`);
|
||||
}
|
||||
const address = utils.getAddress(libraries[contractName]).toLowerCase().slice(2);
|
||||
linkReferences[fileName][contractName].forEach(({ start: byteStart, length: byteLength }) => {
|
||||
const start = 2 + byteStart * 2;
|
||||
const length = byteLength * 2;
|
||||
bytecode = bytecode
|
||||
.slice(0, start)
|
||||
.concat(address)
|
||||
.concat(bytecode.slice(start + length, bytecode.length));
|
||||
});
|
||||
});
|
||||
});
|
||||
return bytecode;
|
||||
}
|
||||
|
||||
export async function testCreatePool (
|
||||
uniClient: UniClient,
|
||||
factory: Contract,
|
||||
token0: Contract,
|
||||
token1: Contract,
|
||||
poolAbi: any,
|
||||
signer: Signer,
|
||||
fee: number): Promise<Contract> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
try {
|
||||
const subscription = await uniClient.watchEvents((value: any) => {
|
||||
// Function gets called with previous events. Check for PoolCreatedEvent.
|
||||
if (value.event.__typename === 'PoolCreatedEvent') {
|
||||
expect(value.block).to.not.be.empty;
|
||||
expect(value.tx).to.not.be.empty;
|
||||
expect(value.contract).to.equal(factory.address);
|
||||
expect(value.eventIndex).to.be.a('number');
|
||||
expect(value.event.__typename).to.equal('PoolCreatedEvent');
|
||||
|
||||
const tokens = new Set([token0.address, token1.address]);
|
||||
expect(new Set([value.event.token0, value.event.token1])).to.eql(tokens);
|
||||
expect(value.event.fee).to.equal(fee.toString());
|
||||
expect(value.event.tickSpacing).to.not.be.empty;
|
||||
expect(value.event.pool).to.not.be.empty;
|
||||
|
||||
expect(value.proof).to.not.be.empty;
|
||||
const poolAddress = value.event.pool;
|
||||
const pool = new ethers.Contract(poolAddress, poolAbi, signer);
|
||||
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
resolve(pool);
|
||||
}
|
||||
});
|
||||
|
||||
// Create pool.
|
||||
await factory.createPool(token0.address, token1.address, fee);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
export function testInitialize (
|
||||
uniClient: UniClient,
|
||||
pool: Contract,
|
||||
expectedTick: number,
|
||||
sqrtPrice: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
(async () => {
|
||||
// Subscribe using UniClient.
|
||||
const subscription = await uniClient.watchEvents((value: any) => {
|
||||
// Function gets called with previous events. Check for InitializeEvent.
|
||||
if (value.event.__typename === 'InitializeEvent') {
|
||||
expect(value.block).to.not.be.empty;
|
||||
expect(value.tx).to.not.be.empty;
|
||||
expect(value.contract).to.equal(pool.address);
|
||||
expect(value.eventIndex).to.be.a('number');
|
||||
|
||||
expect(value.event.__typename).to.equal('InitializeEvent');
|
||||
expect(value.event.sqrtPriceX96).to.equal(sqrtPrice);
|
||||
expect(value.event.tick).to.equal(expectedTick.toString());
|
||||
|
||||
expect(value.proof).to.not.be.empty;
|
||||
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Pool initialize.
|
||||
await pool.initialize(BigNumber.from(sqrtPrice));
|
||||
})();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
72
yarn.lock
72
yarn.lock
@ -1890,6 +1890,11 @@
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^7.2.3"
|
||||
|
||||
"@openzeppelin/contracts@3.4.1-solc-0.7-2":
|
||||
version "3.4.1-solc-0.7-2"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92"
|
||||
integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q==
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
@ -2576,6 +2581,33 @@
|
||||
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
|
||||
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
|
||||
|
||||
"@uniswap/lib@^4.0.1-alpha":
|
||||
version "4.0.1-alpha"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02"
|
||||
integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==
|
||||
|
||||
"@uniswap/v2-core@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425"
|
||||
integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==
|
||||
|
||||
"@uniswap/v3-core@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d"
|
||||
integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA==
|
||||
|
||||
"@uniswap/v3-periphery@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.1.1.tgz#be6dfca7b29318ea0d76a7baf15d3b33c3c5e90a"
|
||||
integrity sha512-orqD2Xy4lxVPF6pxd7ECSJY0gzEuqyeVSDHjzM86uWxOXlA4Nlh5pvI959KaS32pSOFBOVVA4XbbZywbJj+CZg==
|
||||
dependencies:
|
||||
"@openzeppelin/contracts" "3.4.1-solc-0.7-2"
|
||||
"@uniswap/lib" "^4.0.1-alpha"
|
||||
"@uniswap/v2-core" "1.0.1"
|
||||
"@uniswap/v3-core" "1.0.0"
|
||||
base64-sol "1.0.1"
|
||||
hardhat-watcher "^2.1.1"
|
||||
|
||||
"@wry/context@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.0.tgz#f903eceb89d238ef7e8168ed30f4511f92d83e06"
|
||||
@ -2840,7 +2872,7 @@ any-promise@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
|
||||
|
||||
anymatch@~3.1.1:
|
||||
anymatch@~3.1.1, anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||
@ -3785,6 +3817,11 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
base64-sol@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6"
|
||||
integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==
|
||||
|
||||
base@^0.11.1:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||
@ -4358,6 +4395,21 @@ chokidar@3.5.1, chokidar@^3.2.2, chokidar@^3.4.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.1"
|
||||
|
||||
chokidar@^3.4.3:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chownr@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
@ -6755,7 +6807,7 @@ fsevents@~2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
|
||||
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
|
||||
|
||||
fsevents@~2.3.1:
|
||||
fsevents@~2.3.1, fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
@ -6961,7 +7013,7 @@ gitconfiglocal@^1.0.0:
|
||||
dependencies:
|
||||
ini "^1.3.2"
|
||||
|
||||
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0:
|
||||
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
@ -7204,6 +7256,13 @@ hard-rejection@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
|
||||
integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
|
||||
|
||||
hardhat-watcher@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.1.1.tgz#8b05fec429ed45da11808bbf6054a90f3e34c51a"
|
||||
integrity sha512-zilmvxAYD34IofBrwOliQn4z92UiDmt2c949DW4Gokf0vS0qk4YTfVCi/LmUBICThGygNANE3WfnRTpjCJGtDA==
|
||||
dependencies:
|
||||
chokidar "^3.4.3"
|
||||
|
||||
hardhat@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.3.0.tgz#5c29f8b4d08155c3dc8c908af9713fd5079522d5"
|
||||
@ -11131,6 +11190,13 @@ readdirp@~3.5.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
|
Loading…
Reference in New Issue
Block a user