punkkit-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +47 -0
- package/.gitmodules +3 -0
- package/README.md +158 -0
- package/config/auction.config.ts +40 -0
- package/config/env.config.ts +204 -0
- package/config/uniswap.config.ts +107 -0
- package/config/voucher.config.ts +10 -0
- package/contracts/MutiVoucher.sol +78 -0
- package/contracts/auction/ChainXAuction.sol +177 -0
- package/contracts/auction/ChainXAuctionV2.sol +672 -0
- package/contracts/auction/ChainXWrappedETH.sol +80 -0
- package/contracts/auction/ChainYLiquidityManager.sol +57 -0
- package/contracts/auction/ChainYShadowETH.sol +148 -0
- package/contracts/auction/ChainYVault.sol +195 -0
- package/contracts/auction/ChainYVaultCoinbase.sol +276 -0
- package/contracts/auction/ChainYVaultV2.sol +318 -0
- package/contracts/auction/coinbase-and-stake/README.md +55 -0
- package/contracts/auction/coinbase-and-stake/coinbase.sol +142 -0
- package/contracts/auction/coinbase-and-stake/invokeCoinbase.sol +159 -0
- package/contracts/auction/coinbase-and-stake/invokeStake.sol +82 -0
- package/contracts/auction/coinbase-and-stake/stake.sol +92 -0
- package/contracts/auction/interfaces/IUniswapV2Factory.sol +15 -0
- package/contracts/auction/interfaces/IUniswapV2Pair.sol +53 -0
- package/contracts/auction/interfaces/IUniswapV2Router02.sol +25 -0
- package/contracts/auction/interfaces/IUnlockStrategy.sol +18 -0
- package/contracts/auction/libraries/EventParser.sol +32 -0
- package/contracts/auction/libraries/TransactionParser.sol +70 -0
- package/contracts/auction/strategies/MatchResultWithdrawnStrategy.sol +33 -0
- package/contracts/auction/utils/BytesLib.sol +180 -0
- package/contracts/auction/utils/RLPReader.sol +355 -0
- package/contracts/uniswap/Create2.sol +80 -0
- package/contracts/uniswap/DynamicFee.sol +100 -0
- package/contracts/uniswap/Example.sol +35 -0
- package/contracts/uniswap/HookMiner.sol +52 -0
- package/contracts/uniswap/LimitOrder.sol +486 -0
- package/contracts/uniswap/LiquidPool.sol +179 -0
- package/contracts/uniswap/MockERC20.sol +20 -0
- package/hardhat.config.ts +35 -0
- package/ignition/modules/LimitOrder.ts +33 -0
- package/package.json +32 -0
- package/scripts/auction/deploy.ts +23 -0
- package/scripts/auction/deployCoinbase.ts +21 -0
- package/scripts/auction/deployXAuction.ts +23 -0
- package/scripts/auction/deployYVault.ts +22 -0
- package/scripts/deploy_voucher.ts +20 -0
- package/scripts/uniswap/deploy/deploy.ts +65 -0
- package/scripts/uniswap/deploy/deploy_create2.ts +11 -0
- package/scripts/uniswap/deploy/deploy_example.ts +35 -0
- package/scripts/uniswap/deploy/deploy_hooks.ts +74 -0
- package/scripts/uniswap/deploy/deploy_mockERC20.ts +42 -0
- package/scripts/uniswap/deploy/help.ts +96 -0
- package/scripts/uniswap/deploy/init.ts +70 -0
- package/src/auction/chainXAuction.ts +209 -0
- package/src/auction/chainYVault.ts +153 -0
- package/src/auction/event.ts +19 -0
- package/src/auction/serialize.ts +162 -0
- package/src/auction/type.ts +71 -0
- package/src/lib/signer.ts +20 -0
- package/src/lib/unlock.ts +14 -0
- package/src/uniswap/1-marketprice/addLiquidity.ts +80 -0
- package/src/uniswap/1-marketprice/removeLiquidity.ts +63 -0
- package/src/uniswap/1-marketprice/swap.ts +100 -0
- package/src/uniswap/2-limitorder/kill.ts +70 -0
- package/src/uniswap/2-limitorder/place.ts +93 -0
- package/src/uniswap/2-limitorder/withdraw.ts +78 -0
- package/src/uniswap/3-dynamicfee/dynamicfee.ts +321 -0
- package/src/uniswap/lib/ERC20.ts +49 -0
- package/src/uniswap/lib/contract.ts +18 -0
- package/src/uniswap/lib/limitOrder.ts +40 -0
- package/src/uniswap/lib/liqCalculation.ts +152 -0
- package/src/uniswap/lib/listen.ts +57 -0
- package/src/uniswap/lib/pool.ts +62 -0
- package/src/uniswap/lib/swap.ts +8 -0
- package/src/uniswap/lib/types.ts +21 -0
- package/src/uniswap/lib/utils.ts +26 -0
- package/src/uniswap/playgroud/abiencode.ts +21 -0
- package/src/uniswap/playgroud/amount0.ts +47 -0
- package/src/uniswap/playgroud/errordecode.ts +54 -0
- package/src/uniswap/playgroud/errorsigs.ts +86 -0
- package/src/voucher.ts +122 -0
- package/test/auction/ChainXAuctionV2.test.ts +265 -0
- package/test/auction/ChainYVaultV2.test.js +163 -0
- package/test/auction/ChainYVaultV2.test.ts +183 -0
- package/test/auction/auction.test.ts +106 -0
- package/test/connect_punk.test.ts +26 -0
- package/test/create2.test.ts +44 -0
- package/test/normal.ts +43 -0
- package/test/test-config.ts +18 -0
- package/test/uniswap/example.test.ts +62 -0
- package/test/uniswap/limitOrder.test.ts +184 -0
- package/test/uniswap/mockERC20.test.ts +142 -0
- package/test/voucher_hardhat.test.ts +120 -0
- package/test/voucher_punk.test.ts +83 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
|
|
3
|
+
import { getAuctionID, buildAuctionCreatedReceipt, buildAuctionCreatedTx } from "../../src/auction/serialize";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { ethers, network } from "hardhat";
|
|
8
|
+
import { Contract, Wallet, Transaction, ContractTransaction, ContractReceipt } from "ethers";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
interface AuctionValue{
|
|
12
|
+
value: bigint;
|
|
13
|
+
salt: string;
|
|
14
|
+
hashSecret: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("auction", function () {
|
|
18
|
+
let chainXAuction: Contract;
|
|
19
|
+
let chainYVault:Contract;
|
|
20
|
+
let coinBase:Contract;
|
|
21
|
+
|
|
22
|
+
let owner: any;
|
|
23
|
+
let seller: any;
|
|
24
|
+
let sellerWallet: Wallet;
|
|
25
|
+
let bidder1: any;
|
|
26
|
+
let bidder2: any;
|
|
27
|
+
let configId: bigint = BigInt(0);
|
|
28
|
+
let abiCoder=new ethers.utils.AbiCoder();
|
|
29
|
+
|
|
30
|
+
let auctionValue={
|
|
31
|
+
value: BigInt(100),
|
|
32
|
+
salt:ethers.utils.hexZeroPad("0x1234", 32),
|
|
33
|
+
hashSecret: ethers.utils.solidityKeccak256(
|
|
34
|
+
["uint256", "bytes32"],
|
|
35
|
+
[BigInt(100), ethers.utils.hexZeroPad("0x1234", 32)]
|
|
36
|
+
)
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// 测试数据
|
|
40
|
+
const auctionType = 0x00000000; // 密封竞标
|
|
41
|
+
const baseAmount = ethers.utils.parseEther("1.0");
|
|
42
|
+
|
|
43
|
+
const chainXId = 31337;
|
|
44
|
+
const chainYId = 22445;
|
|
45
|
+
|
|
46
|
+
const gasPrice = ethers.utils.parseUnits("1", "gwei").toBigInt();
|
|
47
|
+
const gasLimit = BigInt(52_200);
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
beforeEach(async function () {
|
|
52
|
+
[owner, bidder1, bidder2] = await ethers.getSigners();
|
|
53
|
+
seller = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
|
|
54
|
+
let sellerPk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
|
55
|
+
sellerWallet = new ethers.Wallet(sellerPk);
|
|
56
|
+
|
|
57
|
+
const coinBaseFactory=await ethers.getContractFactory("CoinBase");
|
|
58
|
+
coinBase=await coinBaseFactory.deploy();
|
|
59
|
+
await coinBase.deployed();
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const ChainYVaultFactory=await ethers.getContractFactory("ChainYVaultV2");
|
|
63
|
+
chainYVault=await ChainYVaultFactory.deploy(coinBase.address);
|
|
64
|
+
await chainYVault.deployed();
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const ChainXAuctionV2 = await ethers.getContractFactory("ChainXAuctionV2");
|
|
68
|
+
chainXAuction = await ChainXAuctionV2.deploy(chainYVault.address);
|
|
69
|
+
await chainXAuction.deployed();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("punk链(Y链) 创建拍卖配置", function () {
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
it ("创建拍卖配置",async function(){
|
|
76
|
+
configId = await chainYVault.getActiveConfigsCount();
|
|
77
|
+
await chainYVault.createAuctionConfig(
|
|
78
|
+
auctionType,
|
|
79
|
+
baseAmount,
|
|
80
|
+
86400 // 1天的扩展时间
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it ("Y链创建拍卖",async function(){
|
|
85
|
+
const expiration = Math.floor(Date.now() / 1000) + 3600;
|
|
86
|
+
let auctionCreateTx:ContractTransaction = await chainYVault.connect(seller).createAucion(configId,auctionValue.hashSecret,expiration,{value:baseAmount})
|
|
87
|
+
let auctionCreateReceipt: ContractReceipt=await auctionCreateTx.wait();
|
|
88
|
+
const event = auctionCreateReceipt.events?.find(
|
|
89
|
+
(e: any) => e.event === "AuctionCreated"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(event).to.not.be.undefined;
|
|
93
|
+
const auctionId = event.args.auctionId;
|
|
94
|
+
const auction = await chainYVault.auctions(auctionId);
|
|
95
|
+
expect(auction.auctionType).to.equal(auctionType);
|
|
96
|
+
expect(auction.baseAmount).to.equal(baseAmount);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it ("X链创建拍卖",async function(){
|
|
100
|
+
const crossChainMessage=ethers.utils.defaultAbiCoder.encode(
|
|
101
|
+
["tuple(uint256 sourceChainId, bytes rawTransaction, bytes rawRecpt)"],
|
|
102
|
+
[[chainYId, auctionCreateTx.rawTransaction, ]]
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
import { utils } from "ethers";
|
|
3
|
+
import { ethers } from "hardhat";
|
|
4
|
+
import { MUTI_VOUCHER_ADDR } from "../config/voucher.config";
|
|
5
|
+
import { RPC_URL} from "../config/env.config"
|
|
6
|
+
|
|
7
|
+
async function getCode(address:string){
|
|
8
|
+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
|
9
|
+
const code = await provider.getCode(address);
|
|
10
|
+
if (code === "0x"){
|
|
11
|
+
console.log(`Contract ${address} is not deployed`);
|
|
12
|
+
}else{
|
|
13
|
+
console.log("contract code:",code)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 判断是否链接到punk链
|
|
18
|
+
// npx hardhat run test/connect_punk.test.ts --network punk 命令可以执行
|
|
19
|
+
// TODO: 转化为test风格
|
|
20
|
+
function main(){
|
|
21
|
+
getCode(MUTI_VOUCHER_ADDR);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (require.main === module){
|
|
25
|
+
main();
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { Contract, BigNumber } from "ethers";
|
|
4
|
+
import { CONTRACT_ADDRESSES } from "../config/uniswap.config";
|
|
5
|
+
|
|
6
|
+
describe("create2 test", function () {
|
|
7
|
+
let value = BigInt(1000);
|
|
8
|
+
let create2: Contract;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
let [deployer] = await ethers.getSigners();
|
|
12
|
+
let Factory = await ethers.getContractFactory("Create2", deployer);
|
|
13
|
+
create2 = await Factory.deploy();
|
|
14
|
+
await create2.deployed();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("deploy example.sol", async function () {
|
|
18
|
+
|
|
19
|
+
const Contract = await ethers.getContractFactory("Example");
|
|
20
|
+
const contract = await Contract.deploy(BigInt(1000));
|
|
21
|
+
await contract.deployed();
|
|
22
|
+
|
|
23
|
+
const storedValue: BigNumber = await contract.getValue();
|
|
24
|
+
expect(storedValue.toBigInt()).to.equal(value);
|
|
25
|
+
|
|
26
|
+
const newValue = BigInt(2000);
|
|
27
|
+
const tx = await contract.setValue(newValue);
|
|
28
|
+
await tx.wait();
|
|
29
|
+
|
|
30
|
+
const updatedValue: BigNumber = await contract.getValue();
|
|
31
|
+
expect(updatedValue.toBigInt()).to.equal(newValue);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("judge address", async function () {
|
|
35
|
+
const tokenName = "Token0";
|
|
36
|
+
let tokenAddress = CONTRACT_ADDRESSES[tokenName];
|
|
37
|
+
tokenAddress = "0xa8aAB7BbAfC9bb277332b25B3C5BCA74534Df4A7"
|
|
38
|
+
if (await ethers.provider.getCode(tokenAddress) != "0x") {
|
|
39
|
+
console.log(`${tokenName} is deployed at address: ${tokenAddress}`);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(`${tokenName} is not deployed`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
package/test/normal.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import { utils } from "ethers";
|
|
3
|
+
import { ethers } from "hardhat";
|
|
4
|
+
import { RPC_URL,CONTRACTS, CONTRACT_ADDRESSES } from "../config/uniswap.config";
|
|
5
|
+
|
|
6
|
+
async function getCode(address:string){
|
|
7
|
+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
|
8
|
+
const code = await provider.getCode(address);
|
|
9
|
+
if (code === "0x"){
|
|
10
|
+
console.log(`Contract ${address} is not deployed`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function ConvertBytes32(){
|
|
15
|
+
const voucherName="BitCoin"
|
|
16
|
+
const voucherNameByte32=ethers.utils.formatBytes32String(voucherName);
|
|
17
|
+
console.log("voucherNameByte32:",voucherNameByte32);
|
|
18
|
+
|
|
19
|
+
let parseNameString=ethers.utils.parseBytes32String(voucherNameByte32);
|
|
20
|
+
console.log("parseNameString:",parseNameString);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
function main(){
|
|
25
|
+
const name = "InvalidTickLower()";
|
|
26
|
+
const selector = utils.id(name).substring(0, 10)
|
|
27
|
+
console.log("%s abi encode:%s",name,selector);
|
|
28
|
+
|
|
29
|
+
ConvertBytes32()
|
|
30
|
+
// const encoded = utils.defaultAbiCoder.encode(["string"], [name]);
|
|
31
|
+
// console.log(encoded);
|
|
32
|
+
// getCode(CONTRACT_ADDRESSES.Create2);
|
|
33
|
+
// getCode(CONTRACT_ADDRESSES.Token0);
|
|
34
|
+
// getCode(CONTRACT_ADDRESSES.Token1);
|
|
35
|
+
// getCode(CONTRACT_ADDRESSES.PoolManager);
|
|
36
|
+
// getCode(CONTRACT_ADDRESSES.LiquidPool);
|
|
37
|
+
// getCode(CONTRACT_ADDRESSES.LimitOrder);
|
|
38
|
+
// getCode(CONTRACT_ADDRESSES.DynamicFee);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (require.main === module){
|
|
42
|
+
main();
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { config } from "../config/env.config";
|
|
2
|
+
|
|
3
|
+
console.log("=== 配置验证 ===\n");
|
|
4
|
+
|
|
5
|
+
console.log("ABI 根目录:", config.paths.abiRootDir);
|
|
6
|
+
console.log("\n完整 ABI 路径:");
|
|
7
|
+
console.log("- PoolManager:", config.abiPaths.uniswap.poolManager);
|
|
8
|
+
console.log("- MockERC20:", config.abiPaths.uniswap.mockERC20);
|
|
9
|
+
console.log("- ChainYVault:", config.abiPaths.auction.chainYVault);
|
|
10
|
+
console.log("- ChainXAuction:", config.abiPaths.auction.chainXAuction);
|
|
11
|
+
|
|
12
|
+
console.log("\n合约地址:");
|
|
13
|
+
console.log("- Token0:", config.contracts.uniswap.token0);
|
|
14
|
+
console.log("- PoolManager:", config.contracts.uniswap.poolManager);
|
|
15
|
+
|
|
16
|
+
console.log("\nPool 配置:");
|
|
17
|
+
console.log("- PRICE_INIT:", config.pool.priceInit);
|
|
18
|
+
console.log("- INITIAL_LIQUIDITY:", config.pool.initialLiquidity.toString());
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { Contract, BigNumber } from "ethers";
|
|
4
|
+
|
|
5
|
+
describe("Example", function () {
|
|
6
|
+
let value=BigInt(1000);
|
|
7
|
+
let contract: Contract;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
let [deployer, addr1, addr2] = await ethers.getSigners();
|
|
11
|
+
let Factory = await ethers.getContractFactory("Example", deployer);
|
|
12
|
+
contract = await Factory.deploy(value);
|
|
13
|
+
await contract.deployed();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("initial and store",async function() {
|
|
17
|
+
const storedValue: BigNumber = await contract.getValue();
|
|
18
|
+
expect(storedValue.toBigInt()).to.equal(value);
|
|
19
|
+
|
|
20
|
+
const newValue = BigInt(2000);
|
|
21
|
+
const tx = await contract.setValue(newValue);
|
|
22
|
+
await tx.wait();
|
|
23
|
+
|
|
24
|
+
const updatedValue: BigNumber = await contract.getValue();
|
|
25
|
+
expect(updatedValue.toBigInt()).to.equal(newValue);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("Example2", function () {
|
|
30
|
+
let value="Hello World!";
|
|
31
|
+
let contract: Contract;
|
|
32
|
+
let contractNmae="Example2";
|
|
33
|
+
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
let [deployer] = await ethers.getSigners();
|
|
36
|
+
let Factory = await ethers.getContractFactory(contractNmae, deployer);
|
|
37
|
+
contract = await Factory.deploy();
|
|
38
|
+
await contract.deployed();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("initial and store",async function() {
|
|
42
|
+
const storedValue = await contract.getName();
|
|
43
|
+
console.log(storedValue);
|
|
44
|
+
expect(storedValue).to.equal(value);
|
|
45
|
+
|
|
46
|
+
const tx = await contract.setName(value);
|
|
47
|
+
await tx.wait();
|
|
48
|
+
|
|
49
|
+
const updatedValue = await contract.getName();
|
|
50
|
+
console.log(updatedValue);
|
|
51
|
+
expect(updatedValue).to.equal(value);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("normal test", function () {
|
|
56
|
+
it("hex concat example", function () {
|
|
57
|
+
const hex1 = "0x1111";
|
|
58
|
+
const hex2 = "0x2222";
|
|
59
|
+
const result = ethers.utils.hexConcat([hex1, hex2]);
|
|
60
|
+
console.log(`result: ${result}`);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
// LoadFixture is used to set up a fresh blockchain state for each test
|
|
4
|
+
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
|
5
|
+
|
|
6
|
+
describe("LimitOrder", function () {
|
|
7
|
+
const TICK_SPACING = 10;
|
|
8
|
+
const SWAP_FEE = 3000;
|
|
9
|
+
const sqrtPriceX96 = ethers.utils.parseEther("1");
|
|
10
|
+
|
|
11
|
+
async function deployFixture() {
|
|
12
|
+
const [deployer, user] = await ethers.getSigners();
|
|
13
|
+
|
|
14
|
+
const MockERC20Factory = await ethers.getContractFactory("MockERC20", deployer);
|
|
15
|
+
const token0 = await MockERC20Factory.deploy("Token0", "T0", 18);
|
|
16
|
+
const token1 = await MockERC20Factory.deploy("Token1", "T1", 18);
|
|
17
|
+
|
|
18
|
+
// Ensure token0 is "less than" token1 for pool key purposes
|
|
19
|
+
const [sortedToken0, sortedToken1] =
|
|
20
|
+
(token0.address).toLowerCase() < (token1.address).toLowerCase()
|
|
21
|
+
? [token0, token1]
|
|
22
|
+
: [token1, token0];
|
|
23
|
+
|
|
24
|
+
const PoolManagerFactory = await ethers.getContractFactory("PoolManager", deployer);
|
|
25
|
+
const PoolManager = await PoolManagerFactory.deploy(user.address);
|
|
26
|
+
|
|
27
|
+
const LimitOrderFactory = await ethers.getContractFactory("LimitOrder", deployer);
|
|
28
|
+
const limitOrder = await LimitOrderFactory.deploy(PoolManager.address);
|
|
29
|
+
|
|
30
|
+
const poolKey = {
|
|
31
|
+
currency0: sortedToken0.address,
|
|
32
|
+
currency1: sortedToken1.address,
|
|
33
|
+
fee: SWAP_FEE,
|
|
34
|
+
tickSpacing: TICK_SPACING,
|
|
35
|
+
hooks: limitOrder.address,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Mint some tokens for the user
|
|
39
|
+
const initialAmount = ethers.utils.parseEther("1000");
|
|
40
|
+
await sortedToken0.mint(user.address, initialAmount);
|
|
41
|
+
await sortedToken1.mint(user.address, initialAmount);
|
|
42
|
+
|
|
43
|
+
// User approves PoolManager contract to spend their tokens, as it's the one pulling funds
|
|
44
|
+
await sortedToken0.connect(user).approve(PoolManager.address, ethers.constants.MaxUint256);
|
|
45
|
+
await sortedToken1.connect(user).approve(PoolManager.address, ethers.constants.MaxUint256);
|
|
46
|
+
|
|
47
|
+
// Initialize PoolManager's pool state (mock)
|
|
48
|
+
await PoolManager.initialize(poolKey, sqrtPriceX96);
|
|
49
|
+
|
|
50
|
+
// Initialize add liquidity
|
|
51
|
+
const modifyPositionParams={
|
|
52
|
+
tickLower: -TICK_SPACING * 10,
|
|
53
|
+
tickUpper: TICK_SPACING * 10,
|
|
54
|
+
liquidityDelta: ethers.utils.parseEther("1000"),
|
|
55
|
+
};
|
|
56
|
+
const tx = await PoolManager.addLiquidity(modifyPositionParams, "");
|
|
57
|
+
await tx.wait();
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
deployer,
|
|
61
|
+
user,
|
|
62
|
+
token0: sortedToken0,
|
|
63
|
+
token1: sortedToken1,
|
|
64
|
+
PoolManager,
|
|
65
|
+
limitOrder,
|
|
66
|
+
poolKey,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe("Placing a limit order", function () {
|
|
71
|
+
it("Should allow a user to place a limit order", async function () {
|
|
72
|
+
const { user, limitOrder, poolKey, token0, PoolManager } = await loadFixture(deployFixture);
|
|
73
|
+
const orderAmount = ethers.utils.parseEther("10");
|
|
74
|
+
const tickLower = -10;
|
|
75
|
+
const zeroForOne = true;
|
|
76
|
+
const initialUserBalance = await token0.balanceOf(user.address);
|
|
77
|
+
|
|
78
|
+
await expect(limitOrder.connect(user).place(poolKey, tickLower, zeroForOne, orderAmount))
|
|
79
|
+
.to.emit(limitOrder, "Place");
|
|
80
|
+
|
|
81
|
+
const finalUserBalance = await token0.balanceOf(user.address);
|
|
82
|
+
const poolManagerBalance = await token0.balanceOf(PoolManager.address);
|
|
83
|
+
|
|
84
|
+
// User's balance decreases
|
|
85
|
+
expect(finalUserBalance).to.be.lt(initialUserBalance);
|
|
86
|
+
// PoolManager's balance increases
|
|
87
|
+
expect(poolManagerBalance).to.be.gt(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("Filling a limit order", function () {
|
|
92
|
+
it("Should fill a limit order after a swap", async function () {
|
|
93
|
+
const { user, limitOrder, PoolManager, poolKey, token0, token1 } = await loadFixture(deployFixture);
|
|
94
|
+
const orderAmount = ethers.utils.parseEther("10");
|
|
95
|
+
const tickLower = -10;
|
|
96
|
+
const zeroForOne = true;
|
|
97
|
+
|
|
98
|
+
// Place order
|
|
99
|
+
await limitOrder.connect(user).place(poolKey, tickLower, zeroForOne, orderAmount);
|
|
100
|
+
const epoch = await limitOrder.getEpoch(poolKey, tickLower, zeroForOne);
|
|
101
|
+
|
|
102
|
+
// Simulate a swap that crosses the tick
|
|
103
|
+
// Set current tick on mock pool manager to be past the order's tick
|
|
104
|
+
await PoolManager.setTick(tickLower - TICK_SPACING);
|
|
105
|
+
|
|
106
|
+
const swapParams = {
|
|
107
|
+
zeroForOne: false, // Swap direction is opposite of limit order direction
|
|
108
|
+
amountSpecified: ethers.utils.parseEther("-1"), // negative for exact output
|
|
109
|
+
sqrtPriceLimitX96: 0,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const salt = ethers.utils.formatBytes32String("test");
|
|
113
|
+
const hookData = ethers.utils.defaultAbiCoder.encode(["bytes32"], [salt]);
|
|
114
|
+
|
|
115
|
+
// The pool manager would call this hook after a swap
|
|
116
|
+
await limitOrder.afterSwap(user.address, poolKey, swapParams, "0x", hookData);
|
|
117
|
+
|
|
118
|
+
const epochInfo = await limitOrder.epochInfos(epoch);
|
|
119
|
+
expect(epochInfo.filled).to.be.true;
|
|
120
|
+
|
|
121
|
+
// The limit order contract should now hold the filled tokens (token1 in this case)
|
|
122
|
+
const contractToken1Balance = await token1.balanceOf(limitOrder.address);
|
|
123
|
+
expect(contractToken1Balance).to.be.gt(0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("Killing/cancelling a limit order", function () {
|
|
128
|
+
it("Should allow a user to cancel their limit order", async function () {
|
|
129
|
+
const { user, limitOrder, poolKey, token0 } = await loadFixture(deployFixture);
|
|
130
|
+
const orderAmount = ethers.utils.parseEther("10");
|
|
131
|
+
const tickLower = -10;
|
|
132
|
+
const zeroForOne = true;
|
|
133
|
+
const initialUserBalance = await token0.balanceOf(user.address);
|
|
134
|
+
|
|
135
|
+
// Place order
|
|
136
|
+
await limitOrder.connect(user).place(poolKey, tickLower, zeroForOne, orderAmount);
|
|
137
|
+
|
|
138
|
+
const balanceAfterPlace = await token0.balanceOf(user.address);
|
|
139
|
+
expect(balanceAfterPlace).to.be.lt(initialUserBalance);
|
|
140
|
+
|
|
141
|
+
// Kill order
|
|
142
|
+
await expect(limitOrder.connect(user).kill(poolKey, tickLower, zeroForOne, user.address))
|
|
143
|
+
.to.emit(limitOrder, "Kill");
|
|
144
|
+
|
|
145
|
+
// User should get their tokens back
|
|
146
|
+
const finalUserBalance = await token0.balanceOf(user.address);
|
|
147
|
+
expect(finalUserBalance).to.equal(initialUserBalance);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("Withdrawing from a filled limit order", function () {
|
|
152
|
+
it("Should allow a user to withdraw from a filled order", async function () {
|
|
153
|
+
const { user, limitOrder, PoolManager, poolKey, token1 } = await loadFixture(deployFixture);
|
|
154
|
+
const orderAmount = ethers.utils.parseEther("10");
|
|
155
|
+
const tickLower = -10;
|
|
156
|
+
const zeroForOne = true;
|
|
157
|
+
|
|
158
|
+
// Place order
|
|
159
|
+
await limitOrder.connect(user).place(poolKey, tickLower, zeroForOne, orderAmount);
|
|
160
|
+
const epoch = await limitOrder.getEpoch(poolKey, tickLower, zeroForOne);
|
|
161
|
+
|
|
162
|
+
// Simulate swap and fill
|
|
163
|
+
await PoolManager.setTick(tickLower - TICK_SPACING);
|
|
164
|
+
const swapParams = { zeroForOne: false, amountSpecified: ethers.utils.parseEther("-1"), sqrtPriceLimitX96: 0 };
|
|
165
|
+
const salt = ethers.utils.formatBytes32String("test");
|
|
166
|
+
const hookData = ethers.utils.defaultAbiCoder.encode(["bytes32"], [salt]);
|
|
167
|
+
await limitOrder.afterSwap(user.address, poolKey, swapParams, "0x", hookData);
|
|
168
|
+
|
|
169
|
+
const initialUserToken1Balance = await token1.balanceOf(user.address);
|
|
170
|
+
|
|
171
|
+
// Withdraw
|
|
172
|
+
await expect(limitOrder.connect(user).withdraw(epoch, user.address))
|
|
173
|
+
.to.emit(limitOrder, "Withdraw");
|
|
174
|
+
|
|
175
|
+
// User should have received the filled tokens
|
|
176
|
+
const finalUserToken1Balance = await token1.balanceOf(user.address);
|
|
177
|
+
expect(finalUserToken1Balance).to.be.gt(initialUserToken1Balance);
|
|
178
|
+
|
|
179
|
+
// Contract should have no more of the filled tokens for this order
|
|
180
|
+
const contractToken1Balance = await token1.balanceOf(limitOrder.address);
|
|
181
|
+
expect(contractToken1Balance).to.equal(0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { Contract, Signer, BigNumber } from "ethers";
|
|
4
|
+
import { CONTRACTS, CONTRACT_ADDRESSES, PRIVATE_KEY, RPC_URL } from "../../config/uniswap.config";
|
|
5
|
+
|
|
6
|
+
describe("MockERC20", function () {
|
|
7
|
+
let token: Contract;
|
|
8
|
+
let owner: Signer;
|
|
9
|
+
let addr1: Signer;
|
|
10
|
+
let addr2: Signer;
|
|
11
|
+
|
|
12
|
+
const tokenName = "Mock Token";
|
|
13
|
+
const tokenSymbol = "MCK";
|
|
14
|
+
// 初始供应量为 1000 个代币 (假设18位小数)
|
|
15
|
+
const initialSupply = ethers.utils.parseEther("1000");
|
|
16
|
+
|
|
17
|
+
beforeEach(async function () {
|
|
18
|
+
// 获取测试账户
|
|
19
|
+
[owner, addr1, addr2] = await ethers.getSigners();
|
|
20
|
+
|
|
21
|
+
// 部署合约
|
|
22
|
+
const MockERC20Factory = await ethers.getContractFactory("MockERC20", owner);
|
|
23
|
+
token = await MockERC20Factory.deploy(tokenName, tokenSymbol, initialSupply);
|
|
24
|
+
await token.deployed();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("Deployment", function () {
|
|
28
|
+
it("Should set the right name and symbol", async function () {
|
|
29
|
+
expect(await token.name()).to.equal(tokenName);
|
|
30
|
+
expect(await token.symbol()).to.equal(tokenSymbol);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("Should assign the total supply of tokens to the owner", async function () {
|
|
34
|
+
const ownerAddress = await owner.getAddress();
|
|
35
|
+
const ownerBalance = await token.balanceOf(ownerAddress);
|
|
36
|
+
expect(await token.totalSupply()).to.equal(ownerBalance);
|
|
37
|
+
expect(ownerBalance).to.equal(initialSupply);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("Transactions", function () {
|
|
42
|
+
it("Should transfer tokens between accounts", async function () {
|
|
43
|
+
const ownerAddress = await owner.getAddress();
|
|
44
|
+
const addr1Address = await addr1.getAddress();
|
|
45
|
+
const transferAmount = ethers.utils.parseEther("50");
|
|
46
|
+
|
|
47
|
+
// 从 owner 转 50 个代币到 addr1
|
|
48
|
+
await token.connect(owner).transfer(addr1Address, transferAmount);
|
|
49
|
+
const addr1Balance = await token.balanceOf(addr1Address);
|
|
50
|
+
expect(addr1Balance).to.equal(transferAmount);
|
|
51
|
+
|
|
52
|
+
// 从 addr1 转 20 个代币到 addr2
|
|
53
|
+
const addr2Address = await addr2.getAddress();
|
|
54
|
+
const transferAmount2 = ethers.utils.parseEther("20");
|
|
55
|
+
await token.connect(addr1).transfer(addr2Address, transferAmount2);
|
|
56
|
+
const addr2Balance = await token.balanceOf(addr2Address);
|
|
57
|
+
expect(addr2Balance).to.equal(transferAmount2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("Should fail if sender doesn’t have enough tokens", async function () {
|
|
61
|
+
const ownerAddress = await owner.getAddress();
|
|
62
|
+
const addr1Address = await addr1.getAddress();
|
|
63
|
+
const initialOwnerBalance = await token.balanceOf(ownerAddress);
|
|
64
|
+
|
|
65
|
+
// 尝试从 addr1 (余额为0) 转账
|
|
66
|
+
const value = 1;
|
|
67
|
+
await expect(token.connect(addr1).transfer(ownerAddress, value))
|
|
68
|
+
.to.be.revertedWithCustomError(token, "ERC20InsufficientBalance")
|
|
69
|
+
.withArgs(addr1Address, 0, value); // address,balance,transfer amount
|
|
70
|
+
|
|
71
|
+
// 确认 owner 余额未变
|
|
72
|
+
expect(await token.balanceOf(ownerAddress)).to.equal(initialOwnerBalance);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("Approval", function () {
|
|
77
|
+
it("Should allow a spender to transfer tokens after approval", async function () {
|
|
78
|
+
const ownerAddress = await owner.getAddress();
|
|
79
|
+
const addr1Address = await addr1.getAddress();
|
|
80
|
+
const addr2Address = await addr2.getAddress();
|
|
81
|
+
const amountToApprove = ethers.utils.parseEther("100");
|
|
82
|
+
|
|
83
|
+
// Owner 授权 addr1 可以动用 100 个代币
|
|
84
|
+
await token.connect(owner).approve(addr1Address, amountToApprove);
|
|
85
|
+
const allowance = await token.allowance(ownerAddress, addr1Address);
|
|
86
|
+
expect(allowance).to.equal(amountToApprove);
|
|
87
|
+
|
|
88
|
+
// Addr1 使用 transferFrom 将 owner 的 50 个代币转给 addr2
|
|
89
|
+
const amountToTransfer = ethers.utils.parseEther("50");
|
|
90
|
+
await token.connect(addr1).transferFrom(ownerAddress, addr2Address, amountToTransfer);
|
|
91
|
+
|
|
92
|
+
// 检查余额
|
|
93
|
+
expect(await token.balanceOf(ownerAddress)).to.equal(initialSupply.sub(amountToTransfer));
|
|
94
|
+
expect(await token.balanceOf(addr2Address)).to.equal(amountToTransfer);
|
|
95
|
+
|
|
96
|
+
// 检查剩余授权额度
|
|
97
|
+
const remainingAllowance = await token.allowance(ownerAddress, addr1Address);
|
|
98
|
+
expect(remainingAllowance).to.equal(amountToApprove.sub(amountToTransfer));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("Minting", function () {
|
|
103
|
+
it("Should allow minting new tokens to any address", async function () {
|
|
104
|
+
const addr1Address = await addr1.getAddress();
|
|
105
|
+
const mintAmount = ethers.utils.parseEther("200");
|
|
106
|
+
const initialTotalSupply = await token.totalSupply();
|
|
107
|
+
|
|
108
|
+
// 给 addr1 增发 200 个代ar币
|
|
109
|
+
await token.connect(owner).mint(addr1Address, mintAmount);
|
|
110
|
+
|
|
111
|
+
// 检查 addr1 余额和总供应量
|
|
112
|
+
expect(await token.balanceOf(addr1Address)).to.equal(mintAmount);
|
|
113
|
+
expect(await token.totalSupply()).to.equal(initialTotalSupply.add(mintAmount));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("Deterministic deployment of MockERC20", function () {
|
|
119
|
+
const tokenName = "BitCoin"
|
|
120
|
+
const tokenSymbol = "BTC"
|
|
121
|
+
const initialSupply = ethers.utils.parseUnits("2100000", 18).toBigInt();
|
|
122
|
+
let walletAddress = "0x";
|
|
123
|
+
let token0Address = CONTRACT_ADDRESSES["Token0"];
|
|
124
|
+
let wallet: Signer;
|
|
125
|
+
let token: Contract;
|
|
126
|
+
|
|
127
|
+
beforeEach(async function () {
|
|
128
|
+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
|
129
|
+
wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
130
|
+
walletAddress = await wallet.getAddress();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("Owner", async function () {
|
|
134
|
+
const token = await ethers.getContractAt("MockERC20", token0Address, wallet);
|
|
135
|
+
let res: bigint = await token.balanceOf(walletAddress);
|
|
136
|
+
console.log(res.toString());
|
|
137
|
+
|
|
138
|
+
res = await token.balanceOf(CONTRACT_ADDRESSES["Create2"]);
|
|
139
|
+
console.log(res.toString())
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
});
|