Skip to content

Commit

Permalink
test: Added draft tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xJurassicPunk committed Apr 13, 2022
1 parent 1cd7042 commit e53e2ca
Show file tree
Hide file tree
Showing 7 changed files with 1,483 additions and 0 deletions.
73 changes: 73 additions & 0 deletions test/helpers/block-traveller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { BigNumber } from "ethers";
import { ethers, network } from "hardhat";

/**
* Advance the state by one block
*/
export async function advanceBlock(): Promise<void> {
await network.provider.send("evm_mine");
}

/**
* Advance the block to the passed target block
* @param targetBlock target block number
* @dev If target block is lower/equal to current block, it throws an error
*/
export async function advanceBlockTo(targetBlock: BigNumber): Promise<void> {
const currentBlock = await ethers.provider.getBlockNumber();
if (targetBlock.lt(currentBlock)) {
throw Error(`Target·block·#(${targetBlock})·is·lower·than·current·block·#(${currentBlock})`);
}

let numberBlocks = targetBlock.sub(currentBlock);

// hardhat_mine only can move by 256 blocks (256 in hex is 0x100)
while (numberBlocks.gte(BigNumber.from("256"))) {
await network.provider.send("hardhat_mine", ["0x100"]);
numberBlocks = numberBlocks.sub(BigNumber.from("256"));
}

if (numberBlocks.eq("1")) {
await network.provider.send("evm_mine");
} else if (numberBlocks.eq("15")) {
// Issue with conversion from hexString of 15 (0x0f instead of 0xF)
await network.provider.send("hardhat_mine", ["0xF"]);
} else {
await network.provider.send("hardhat_mine", [numberBlocks.toHexString()]);
}
}

/**
* Advance the block time to target time
* @param targetTime target time (epoch)
* @dev If target time is lower/equal to current time, it throws an error
*/
export async function increaseTo(targetTime: BigNumber): Promise<void> {
const currentTime = BigNumber.from(await latest());
if (targetTime.lt(currentTime)) {
throw Error(`Target·time·(${targetTime})·is·lower·than·current·time·#(${currentTime})`);
}

await network.provider.send("evm_setNextBlockTimestamp", [targetTime.toHexString()]);
}

/**
* Fetch the current block number
*/
export async function latest(): Promise<number> {
return (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp;
}

/**
* Start automine
*/
export async function pauseAutomine(): Promise<void> {
await network.provider.send("evm_setAutomine", [false]);
}

/**
* Resume automine
*/
export async function resumeAutomine(): Promise<void> {
await network.provider.send("evm_setAutomine", [true]);
}
30 changes: 30 additions & 0 deletions test/helpers/cryptography.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MerkleTree } from "merkletreejs";
import { utils } from "ethers/lib/ethers";
/* eslint-disable node/no-extraneous-import */
import { keccak256 } from "js-sha3";

/**
* Compute the cryptographic hash using keccak256
* @param user address of the user
* @param amount amount for a user
* @dev Do not forget to multiply by 10e18 for decimals
*/
export function computeHash(user: string, amount: string): Buffer {
return Buffer.from(utils.solidityKeccak256(["address", "uint256"], [user, amount]).slice(2), "hex");
}

/**
* Compute a merkle tree and return the tree with its root
* @param tree merkle tree
* @returns 2-tuple with merkle tree object and hexRoot
*/
export function createMerkleTree(tree: Record<string, string>): [MerkleTree, string] {
const merkleTree = new MerkleTree(
Object.entries(tree).map((data) => computeHash(...data)),
keccak256,
{ sortPairs: true }
);

const hexRoot = merkleTree.getHexRoot();
return [merkleTree, hexRoot];
}
18 changes: 18 additions & 0 deletions test/helpers/hardhat-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function findPrivateKey(publicKey: string): string {
switch (publicKey.toLowerCase()) {
case "0x70997970c51812dc3a010c7d01b50e0d17dc79c8":
return "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";

case "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":
return "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a";

case "0x90f79bf6eb2c4f870365e785982e1f101e93b906":
return "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6";

case "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":
return "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a";

default:
return "0x";
}
}
74 changes: 74 additions & 0 deletions test/helpers/order-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { MakerOrder, MakerOrderWithSignature, TakerOrder } from "./order-types";
import { signMakerOrder } from "./signature-helper";

export interface SignedMakerOrder extends MakerOrder {
signerUser: SignerWithAddress;
verifyingContract: string;
}

export async function createMakerOrder({
isOrderAsk,
signer,
collection,
price,
tokenId,
amount,
strategy,
currency,
nonce,
startTime,
endTime,
minPercentageToAsk,
params,
signerUser,
verifyingContract,
}: SignedMakerOrder): Promise<MakerOrderWithSignature> {
const makerOrder: MakerOrder = {
isOrderAsk: isOrderAsk,
signer: signer,
collection: collection,
price: price,
tokenId: tokenId,
amount: amount,
strategy: strategy,
currency: currency,
nonce: nonce,
startTime: startTime,
endTime: endTime,
minPercentageToAsk: minPercentageToAsk,
params: params,
};

const signedOrder = await signMakerOrder(signerUser, verifyingContract, makerOrder);

// Extend makerOrder with proper signature
const makerOrderExtended: MakerOrderWithSignature = {
...makerOrder,
r: signedOrder.r,
s: signedOrder.s,
v: signedOrder.v,
};

return makerOrderExtended;
}

export function createTakerOrder({
isOrderAsk,
taker,
price,
tokenId,
minPercentageToAsk,
params,
}: TakerOrder): TakerOrder {
const takerOrder: TakerOrder = {
isOrderAsk: isOrderAsk,
taker: taker,
price: price,
tokenId: tokenId,
minPercentageToAsk: minPercentageToAsk,
params: params,
};

return takerOrder;
}
32 changes: 32 additions & 0 deletions test/helpers/order-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { BigNumber, BigNumberish, BytesLike } from "ethers";

export interface MakerOrder {
isOrderAsk: boolean; // true if ask, false if bid
signer: string; // signer address of the maker order
collection: string; // collection address
price: BigNumber; // price
tokenId: BigNumber; // id of the token
amount: BigNumber; // amount of tokens to purchase
strategy: string; // strategy address for trade execution
currency: string; // currency address
nonce: BigNumber; // order nonce
minPercentageToAsk: BigNumber;
startTime: BigNumber; // startTime in epoch
endTime: BigNumber; // endTime in epoch
params: BytesLike; // additional parameters
}

export interface MakerOrderWithSignature extends MakerOrder {
r: BytesLike; // r: parameter
s: BytesLike; // s: parameter
v: BigNumberish; // v: parameter (27 or 28)
}

export interface TakerOrder {
isOrderAsk: boolean; // true if ask, false if bid
taker: string; // Taker address
price: BigNumber; // price for the purchase
tokenId: BigNumber;
minPercentageToAsk: BigNumber;
params: BytesLike; // params (e.g., tokenId)
}
150 changes: 150 additions & 0 deletions test/helpers/signature-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { BigNumber, utils, Wallet } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
/* eslint-disable node/no-extraneous-import */
import { TypedDataDomain } from "@ethersproject/abstract-signer";
/* eslint-disable node/no-extraneous-import */
import { Signature } from "@ethersproject/bytes";
/* eslint-disable node/no-extraneous-import */
import { _TypedDataEncoder } from "@ethersproject/hash";
import { MakerOrder } from "./order-types";
import { findPrivateKey } from "./hardhat-keys";

const { defaultAbiCoder, keccak256, solidityPack } = utils;

/**
* Generate a signature used to generate v, r, s parameters
* @param signer signer
* @param types solidity types of the value param
* @param values params to be sent to the Solidity function
* @param verifyingContract verifying contract address ("LooksRareExchange")
* @returns splitted signature
* @see https://docs.ethers.io/v5/api/signer/#Signer-signTypedData
*/
const signTypedData = async (
signer: SignerWithAddress,
types: string[],
values: (string | boolean | BigNumber)[],
verifyingContract: string
): Promise<Signature> => {
const domain: TypedDataDomain = {
name: "LooksRareExchange",
version: "1",
chainId: "31337", // HRE
verifyingContract: verifyingContract,
};

const domainSeparator = _TypedDataEncoder.hashDomain(domain);

// https://docs.ethers.io/v5/api/utils/abi/coder/#AbiCoder--methods
const hash = keccak256(defaultAbiCoder.encode(types, values));

// Compute the digest
const digest = keccak256(
solidityPack(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", domainSeparator, hash])
);

const adjustedSigner = new Wallet(findPrivateKey(signer.address));
return { ...adjustedSigner._signingKey().signDigest(digest) };
};

export const computeDomainSeparator = (verifyingContract: string): string => {
const domain: TypedDataDomain = {
name: "LooksRareExchange",
version: "1",
chainId: "31337", // HRE
verifyingContract: verifyingContract,
};

return _TypedDataEncoder.hashDomain(domain);
};
/**
* Compute order hash for a maker order
* @param order MakerOrder
* @returns hash
*/
export const computeOrderHash = (order: MakerOrder): string => {
const types = [
"bytes32",
"bool",
"address",
"address",
"uint256",
"uint256",
"uint256",
"address",
"address",
"uint256",
"uint256",
"uint256",
"uint256",
"bytes32",
];

const values = [
"0x40261ade532fa1d2c7293df30aaadb9b3c616fae525a0b56d3d411c841a85028", // maker order hash (from Solidity)
order.isOrderAsk,
order.signer,
order.collection,
order.price,
order.tokenId,
order.amount,
order.strategy,
order.currency,
order.nonce,
order.startTime,
order.endTime,
order.minPercentageToAsk,
keccak256(order.params),
];

return keccak256(defaultAbiCoder.encode(types, values));
};

/**
* Create a signature for a maker order
* @param signer signer for the order
* @param verifyingContract verifying contract address
* @param order see MakerOrder definition
* @returns splitted signature
*/
export const signMakerOrder = (
signer: SignerWithAddress,
verifyingContract: string,
order: MakerOrder
): Promise<Signature> => {
const types = [
"bytes32",
"bool",
"address",
"address",
"uint256",
"uint256",
"uint256",
"address",
"address",
"uint256",
"uint256",
"uint256",
"uint256",
"bytes32",
];

const values = [
"0x40261ade532fa1d2c7293df30aaadb9b3c616fae525a0b56d3d411c841a85028",
order.isOrderAsk,
order.signer,
order.collection,
order.price,
order.tokenId,
order.amount,
order.strategy,
order.currency,
order.nonce,
order.startTime,
order.endTime,
order.minPercentageToAsk,
keccak256(order.params),
];

return signTypedData(signer, types, values, verifyingContract);
};
Loading

0 comments on commit e53e2ca

Please sign in to comment.