@luckyfinance/hardhat-framework
Version:
Common test utilities for hardhat development
421 lines (373 loc) • 17.3 kB
JavaScript
const config = require('./config')
const {
BigNumber,
utils: { keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack },
} = require("ethers")
const { ecsign } = require("ethereumjs-util")
const { BN } = require("bn.js")
const { GoldVeinPair } = require("./goldveinpair")
const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"
const BASE_TEN = 10
const PERMIT_TYPEHASH = keccak256(toUtf8Bytes("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"))
const ALPINE_MASTER_APPROVAL_TYPEHASH = keccak256(
toUtf8Bytes("SetMasterContractApproval(string warning,address user,address masterContract,bool approved,uint256 nonce)")
)
const contracts = {}
function roundBN(number) {
return new BN(number.toString()).divRound(new BN("10000000000000000")).toString()
}
function encodePrice(reserve0, reserve1) {
return [reserve1.mul(getBigNumber(1)).div(reserve0), reserve0.mul(getBigNumber(1)).div(reserve1)]
}
function getDomainSeparator(tokenAddress, chainId) {
return keccak256(
defaultAbiCoder.encode(
["bytes32", "uint256", "address"],
[keccak256(toUtf8Bytes("EIP712Domain(uint256 chainId,address verifyingContract)")), chainId, tokenAddress]
)
)
}
function getApprovalDigest(token, approve, nonce, deadline, chainId = 1) {
const DOMAIN_SEPARATOR = getDomainSeparator(token.address, chainId)
const msg = defaultAbiCoder.encode(
["bytes32", "address", "address", "uint256", "uint256", "uint256"],
[PERMIT_TYPEHASH, approve.owner, approve.spender, approve.value, nonce, deadline]
)
const pack = solidityPack(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", DOMAIN_SEPARATOR, keccak256(msg)])
return keccak256(pack)
}
function getApprovalMsg(tokenAddress, approve, nonce, deadline) {
const DOMAIN_SEPARATOR = getDomainSeparator(tokenAddress)
const msg = defaultAbiCoder.encode(
["bytes32", "address", "address", "uint256", "uint256", "uint256"],
[PERMIT_TYPEHASH, approve.owner, approve.spender, approve.value, nonce, deadline]
)
const pack = solidityPack(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", DOMAIN_SEPARATOR, keccak256(msg)])
return pack
}
function getAlpineDomainSeparator(address, chainId) {
return keccak256(
defaultAbiCoder.encode(
["bytes32", "bytes32", "uint256", "address"],
[keccak256(toUtf8Bytes("EIP712Domain(string name,uint256 chainId,address verifyingContract)")), keccak256(toUtf8Bytes("Alpine V1")), chainId, address]
)
)
}
function getAlpineApprovalDigest(alPine, user, masterContractAddress, approved, nonce, chainId = 1) {
const DOMAIN_SEPARATOR = getAlpineDomainSeparator(alPine.address, chainId)
const msg = defaultAbiCoder.encode(
["bytes32", "bytes32", "address", "address", "bool", "uint256"],
[
ALPINE_MASTER_APPROVAL_TYPEHASH,
keccak256(toUtf8Bytes(approved ? "Give FULL access to funds in (and approved to) Alpine?" : "Revoke access to Alpine?")),
user.address,
masterContractAddress,
approved,
nonce,
]
)
const pack = solidityPack(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", DOMAIN_SEPARATOR, keccak256(msg)])
return keccak256(pack)
}
function getSignedMasterContractApprovalData(alPine, user, privateKey, masterContractAddress, approved, nonce) {
const digest = getAlpineApprovalDigest(alPine, user, masterContractAddress, approved, nonce, user.provider._network.chainId)
const { v, r, s } = ecsign(Buffer.from(digest.slice(2), "hex"), Buffer.from(privateKey.replace("0x", ""), "hex"))
return { v, r, s }
}
async function setMasterContractApproval(alPine, from, user, privateKey, masterContractAddress, approved, fallback) {
if (!fallback) {
const nonce = await alPine.nonces(user.address)
const digest = getAlpineApprovalDigest(alPine, user, masterContractAddress, approved, nonce, user.provider._network.chainId)
const { v, r, s } = ecsign(Buffer.from(digest.slice(2), "hex"), Buffer.from(privateKey.replace("0x", ""), "hex"))
return await alPine.connect(user).setMasterContractApproval(from.address, masterContractAddress, approved, v, r, s)
}
return await alPine
.connect(user)
.setMasterContractApproval(
from.address,
masterContractAddress,
approved,
0,
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000"
)
}
async function setGoldVeinPairContractApproval(alPine, user, privateKey, goldveinPair, approved) {
const nonce = await alPine.nonces(user.address)
const digest = getAlpineApprovalDigest(alPine, user, goldveinPair.address, approved, nonce, user.provider._network.chainId)
const { v, r, s } = ecsign(Buffer.from(digest.slice(2), "hex"), Buffer.from(privateKey.replace("0x", ""), "hex"))
return await goldveinPair.connect(user).setApproval(user.address, approved, v, r, s)
}
async function goldveinPairPermit(alPine, token, user, privateKey, goldveinPair, amount) {
const nonce = await token.nonces(user.address)
const deadline = (await user.provider._internalBlockNumber).respTime + 10000
const digest = await getApprovalDigest(
token,
{
owner: user.address,
spender: alPine.address,
value: amount,
},
nonce,
deadline,
user.provider._network.chainId
)
const { v, r, s } = ecsign(Buffer.from(digest.slice(2), "hex"), Buffer.from(privateKey.replace("0x", ""), "hex"))
return await goldveinPair.connect(user).permitToken(token.address, user.address, alPine.address, amount, deadline, v, r, s)
}
function sansBorrowFee(amount) {
return amount.mul(BigNumber.from(2000)).div(BigNumber.from(2001))
}
function sansSafetyAmount(amount) {
return amount.sub(BigNumber.from(100000))
}
async function advanceTimeAndBlock(time, ethers) {
await advanceTime(time, ethers)
await advanceBlock(ethers)
}
async function advanceTime(time, ethers) {
await ethers.provider.send("evm_increaseTime", [time])
}
async function advanceBlock(ethers) {
await ethers.provider.send("evm_mine")
}
// Defaults to e18 using amount * 10^18
function getBigNumber(amount, decimals = 18) {
return BigNumber.from(amount).mul(BigNumber.from(BASE_TEN).pow(decimals))
}
function addContract(thisObject, name, contract) {
thisObject[name] = contract
contract.thisName = name
contracts[contract.address] = contract
}
async function getContract(thisObject, var_name, name) {
const contract = await ethers.getContract(name)
addContract(thisObject, var_name, contract)
return contract
}
async function analyse(thisObject, tx_promise) {
let tx
try {
tx = await tx_promise
} catch (e) {
const revertMsg = e.message.replace("VM Exception while processing transaction: revert ", "")
if (revertMsg) {
console.log('.to.be.revertedWith("' + revertMsg + '")')
} else {
console.log(".to.be.reverted")
}
return
}
const rx = await thisObject.alice.provider.getTransactionReceipt(tx.hash)
const logs = decodeLogs(rx.logs)
for (var i in logs) {
var log = logs[i]
console.log(".to.emit(this." + log.contract_name + ', "' + log.name + '")')
console.log(".withArgs(" + log.args.join(", ") + ")")
}
}
function weth(chainId) {
return {
1: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // Mainnet
3: "0xc778417E063141139Fce010982780140Aa0cD5Ab", // Ropsten
4: "0xc778417E063141139Fce010982780140Aa0cD5Ab", // Rinkeby
5: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // Gorli
42: "0xd0A1E359811322d97991E03f863a0C30C2cF029C", // Kovan
56: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", // Binance
88: "0xB1f66997A5760428D3a87D68b90BfE0aE64121cC", // TomoChain
89: "0xB837c744A16A7f133A750254270Dce792dBBAE77", // TomoChain Testnet
97: "0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd", // Binance Testnet
100: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", // xDAI
128: "0x5545153ccfca01fbd7dd11c0b23ba694d9509a6f", // Huobi ECO Chain
137: "0x084666322d3ee89aAbDBBCd084323c9AF705C7f5", // Matic
250: "0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83", // Fantom
256: "0x7af326b6351c8a9b8fb8cd205cbe11d4ac5fa836", // Huobi ECO Testnet
4002: "0xf1277d1ed8ad466beddf92ef448a132661956621", // Fantom Testnet
1287: "0x1Ff68A3621C17a38E689E5332Efcab9e6bE88b5D", // Moonbeam Testnet
31337: "", // Hardhat
43113: "0xd00ae08403B9bbb9124bB305C09058E32C39A48c", // Fuji Testnet (Avalanche)
43114: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", // Avalanche
80001: "0x5B67676a984807a212b1c59eBFc9B3568a474F0a", // Mumbai Testnet (MATIC)
79377087078960: "0xf8456e5e6A225C2C1D74D8C9a4cB2B1d5dc1153b", // Arbitrum Testnet
}[chainId.toString()]
}
function nativeTokenSymbol(chainId) {
return {
1: "ETH", // Mainnet
3: "ETH", // Ropsten
4: "ETH", // Rinkeby
5: "ETH", // Goerli
42: "ETH", // Kovan
56: "BNB", // Binance
88: "TOMO", //TomoChain
89: "TOMO", // TomoChain Testnet
97: "BNB", // Binance Testnet
100: "XDAI", // xDAI
128: "HT", // Huobi ECO Chain
137: "MATIC", // Matic
250: "FTM", // Fantom
256: "HT", // Huobi ECO Testnet
4002: "FTM", // Fantom Testnet
1287: "ETH", // Moonbeam Testnet
43113: "AVAX", // Fuji Testnet (Avalanche)
43114: "AVAX", // Avalanche
80001: "MATIC", // Mumbai Testnet (MATIC)
79377087078960: "ETH", // Arbitrum Testnet
}[chainId.toString()]
}
function explorer(chainId) {
return {
1: "https://etherscan.io", // Mainnet
3: "https://ropsten.etherscan.io", // Ropsten
4: "https://rinkeby.etherscan.io", // Rinkeby
5: "https://goerli.etherscan.io", // Goerli
42: "https://kovan.etherscan.io", // Kovan
56: "https://bscscan.com", // Binance
88: "https://scan.tomochain.com", //TomoChain
89: "https://scan.testnet.tomochain.com", // TomoChain Testnet
97: "https://testnet.bscscan.com", // Binance Testnet
100: "https://blockscout.com/poa/xdai", // xDAI
128: "https://hecoinfo.com", // Huobi ECO Chain
137: "https://explorer-mainnet.maticvigil.com", // Matic
250: "https://ftmscan.com", // Fantom
256: "", // Huobi ECO Testnet
4002: "https://explorer.testnet.fantom.network", // Fantom Testnet
1287: "https://moonbeam-explorer.netlify.app", // Moonbeam Testnet
43113: "https://cchain.explorer.avax-test.network", // Fuji Testnet (Avalanche)
43114: "https://cchain.explorer.avax.network", // Avalanche
80001: "https://explorer-mumbai.maticvigil.com", // Mumbai Testnet (MATIC)
79377087078960: "https://explorer.offchainlabs.com/#", // Arbitrum Testnet
}[chainId.toString()]
}
async function createFixture(deployments, thisObject, stepsFunction) {
return deployments.createFixture(async ({ deployments, getNamedAccounts, ethers }, options) => {
const { deployer } = await getNamedAccounts()
await deployments.fixture()
thisObject.signers = await ethers.getSigners()
addContract(thisObject, "alice", thisObject.signers[0])
addContract(thisObject, "bob", thisObject.signers[1])
addContract(thisObject, "carol", thisObject.signers[2])
addContract(thisObject, "dirk", thisObject.signers[3])
addContract(thisObject, "erin", thisObject.signers[4])
addContract(thisObject, "fred", thisObject.signers[5])
thisObject.alicePrivateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
thisObject.bobPrivateKey = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
thisObject.carolPrivateKey = "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
const getContractFunction = async function (contract_name) {
thisObject[contract_name] = await ethers.getContractFactory(contract_name)
thisObject[contract_name].thisObject = thisObject
thisObject[contract_name].new = async function (name, ...params) {
let newContract = await thisObject[contract_name].deploy(...params)
await newContract.deployed()
newContract.factory = thisObject[contract_name]
addContract(thisObject, name, newContract)
return newContract
}
return thisObject[contract_name]
}
const deployFunction = async function (var_name, contract_name, ...params) {
await getContractFunction(contract_name)
const contract = await thisObject[contract_name].new(var_name, ...params)
return contract
}
const cmd = {
getContract: getContractFunction,
deploy: deployFunction,
addToken: async function (var_name, name, symbol, decimals, tokenClassName) {
tokenClassName = tokenClassName || "ReturnFalseERC20Mock"
if (!thisObject[tokenClassName]) {
await getContractFunction(tokenClassName)
}
const token = await thisObject[tokenClassName].new(var_name, name, symbol, decimals, getBigNumber(1000000, decimals))
await token.transfer(thisObject.bob.address, getBigNumber(1000, decimals))
await token.transfer(thisObject.carol.address, getBigNumber(1000, decimals))
await token.transfer(thisObject.fred.address, getBigNumber(1000, decimals))
return token
},
addPair: async function (var_name, tokenA, tokenB, amountA, amountB) {
const createPairTx = await thisObject.factory.createPair(addr(tokenA), addr(tokenB))
const pair = (await createPairTx.wait()).events[0].args.pair
const SushiSwapPairMock = await ethers.getContractFactory("SushiSwapPairMock")
const sushiSwapPair = await SushiSwapPairMock.attach(pair)
addContract(thisObject, var_name, sushiSwapPair)
await tokenA.transfer(sushiSwapPair.address, getBigNumber(amountA, await tokenA.decimals()))
await tokenB.transfer(sushiSwapPair.address, getBigNumber(amountB, await tokenB.decimals()))
await sushiSwapPair.mint(thisObject.alice.address)
return sushiSwapPair
},
addGoldVeinPair: async function (var_name, alPine, masterContract, asset, collateral, oracle, oracleData) {
const helper = await GoldVeinPair.deploy(alPine, masterContract, masterContract.factory, asset, collateral, oracle, oracleData)
addContract(thisObject, var_name, helper)
return helper
},
}
await deployFunction("factory", "SushiSwapFactoryMock")
await stepsFunction(cmd)
return cmd
})
}
function decodeLogs(logs) {
const decoded = []
for (let i in logs) {
const log = logs[i]
let contract = contracts[log.address]
if (contract) {
let decodedLog = contract.interface.parseLog(log)
const easyArgs = []
for (var j in decodedLog.args) {
if (!isNaN(j)) {
const arg = decodedLog.args[j]
//console.log(typeof arg, arg, arg.toString())
if (typeof arg == "string" && contracts[arg]) {
easyArgs.push("this." + contracts[arg].thisName + ".address")
} else {
easyArgs.push('"' + arg.toString() + '"')
}
}
}
decoded.push({
address: contract.address,
name: decodedLog.name,
args: easyArgs,
contract: contract,
contract_name: contract.thisName,
raw: log,
decoded: decodedLog,
})
} else {
console.log("Cannot decode log")
}
}
return decoded
}
function addr(address) {
if (typeof address == "object" && address.address) {
address = address.address
}
return address
}
module.exports = {
ADDRESS_ZERO,
addr,
getDomainSeparator,
getApprovalDigest,
getApprovalMsg,
getAlpineDomainSeparator,
getAlpineApprovalDigest,
goldveinPairPermit,
getSignedMasterContractApprovalData,
setMasterContractApproval,
setGoldVeinPairContractApproval,
sansBorrowFee,
sansSafetyAmount,
encodePrice,
roundBN,
advanceTime,
advanceBlock,
advanceTimeAndBlock,
getBigNumber,
decodeLogs,
weth,
createFixture,
config
}