generated from LooksRare/solidity-template
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1cd7042
commit e53e2ca
Showing
7 changed files
with
1,483 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; |
Oops, something went wrong.