mirror of
https://github.com/luxfi/uma.git
synced 2026-07-26 21:09:10 +00:00
improve(common): convert common to typescript (#3268)
This commit is contained in:
@@ -9,6 +9,7 @@ coverage
|
||||
coverage.json
|
||||
out.log
|
||||
.GckmsOverride.js
|
||||
.GckmsOverride.ts
|
||||
docs
|
||||
modules
|
||||
ui
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Only the browser-safe modules.
|
||||
module.exports = {
|
||||
...require("./src/AbiUtils"),
|
||||
...require("./src/AdminUtils"),
|
||||
...require("./src/Constants"),
|
||||
...require("./src/ContractUtils"),
|
||||
...require("./src/TransactionUtils"),
|
||||
...require("./src/Crypto"),
|
||||
...require("./src/EmpUtils"),
|
||||
...require("./src/EncryptionHelper"),
|
||||
...require("./src/Enums"),
|
||||
...require("./src/FormattingUtils"),
|
||||
...require("./src/ObjectUtils"),
|
||||
...require("./src/PublicNetworks"),
|
||||
...require("./src/Random"),
|
||||
...require("./src/SolcoverConfig"),
|
||||
...require("./src/SolidityTestUtils"),
|
||||
...require("./src/TimeUtils"),
|
||||
...require("./src/VotingUtils"),
|
||||
...require("./src/PriceIdentifierUtils"),
|
||||
...require("./src/MultiDecimalTestHelper"),
|
||||
...require("./src/MultiVersionTestHelpers"),
|
||||
...require("./src/hardhat/fixtures"),
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
const browserSafe = { ...require("./browser.js") };
|
||||
|
||||
const browserUnsafe = {
|
||||
...require("./src/gckms/ManagedSecretProvider"),
|
||||
...require("./src/MetaMaskTruffleProvider"),
|
||||
...require("./src/MigrationUtils"),
|
||||
...require("./src/TruffleConfig"),
|
||||
...require("./src/ProviderUtils"),
|
||||
...require("./src/HardhatConfig"),
|
||||
...require("./src/RetryProvider"),
|
||||
...require("./src/UniswapV3Helpers"),
|
||||
...require("./src/FileHelpers"),
|
||||
};
|
||||
|
||||
module.exports = { ...browserSafe, ...browserUnsafe };
|
||||
@@ -4,9 +4,9 @@
|
||||
"description": "Common js utilities used by other UMA packages",
|
||||
"homepage": "http://umaproject.org",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "index.js",
|
||||
"main": "./dist/index.js",
|
||||
"browser": {
|
||||
"./index.js": "./browser.js"
|
||||
"./index.js": "./dist/browser.js"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"publishConfig": {
|
||||
@@ -25,14 +25,12 @@
|
||||
"url": "https://github.com/UMAprotocol/protocol/issues"
|
||||
},
|
||||
"files": [
|
||||
"/src/**/*.js",
|
||||
"/dist/**/*",
|
||||
"browser.js"
|
||||
"/dist/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ethersproject/bignumber": "^5.0.5",
|
||||
"@google-cloud/kms": "^0.3.0",
|
||||
"@google-cloud/storage": "^2.4.2",
|
||||
"@google-cloud/kms": "^2.3.1",
|
||||
"@google-cloud/storage": "^5.8.5",
|
||||
"@maticnetwork/maticjs": "^2.0.40",
|
||||
"@truffle/hdwallet-provider": "^1.2.3",
|
||||
"@umaprotocol/truffle-ledger-provider": "^1.0.5",
|
||||
@@ -40,7 +38,7 @@
|
||||
"abi-decoder": "github:UMAprotocol/abi-decoder",
|
||||
"bignumber.js": "^8.0.1",
|
||||
"chalk-pipe": "^3.0.0",
|
||||
"dotenv": "^6.2.0",
|
||||
"dotenv": "^9.0.0",
|
||||
"eth-crypto": "^1.7.0",
|
||||
"hardhat": "^2.5.0",
|
||||
"hardhat-deploy": "^0.8.11",
|
||||
@@ -64,6 +62,9 @@
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@types/ethereum-protocol": "^1.0.0",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"ethers": "^5.4.2"
|
||||
"web3-core": "^1.5.0",
|
||||
"web3-eth-contract": "^1.5.0",
|
||||
"ethers": "^5.4.2",
|
||||
"@types/lodash.uniqby": "^4.7.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,39 @@
|
||||
// pulled from the core/build/contracts directory. Example usage:
|
||||
// getAbiDecoder().decodeMethod(data); // This decodes the txn data into the function name and arguments.
|
||||
|
||||
const abiDecoder = require("abi-decoder");
|
||||
import abiDecoder from "abi-decoder";
|
||||
|
||||
function importAll(r) {
|
||||
export type AbiDecoder = typeof abiDecoder;
|
||||
|
||||
interface Context {
|
||||
keys: () => string[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(input: string): any;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function importAll(r: Context): any[] {
|
||||
return r.keys().map(r);
|
||||
}
|
||||
|
||||
function getAllContracts() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function getAllContracts(): any[] {
|
||||
let importedObjects;
|
||||
|
||||
// Note: we use a try here because we don't want to install the require-context package in node.js contexts where
|
||||
// it won't work.
|
||||
if (process.browser) {
|
||||
const castedProcess = (process as unknown) as { browser: true };
|
||||
if (castedProcess.browser) {
|
||||
// This only works in webpack.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const requireContext = require("require-context");
|
||||
require("require-context");
|
||||
|
||||
// Note: all arguments must be hardcoded here for webpack to bundle the files correctly.
|
||||
// This line also generates a few build warnings that should be ignored.
|
||||
const contractContext = require.context("@uma/core/build/contracts/", true, /\.json$/);
|
||||
const castedRequire = (require as unknown) as {
|
||||
context: (path: string, useSubdirectories: boolean, regex: RegExp) => Context;
|
||||
};
|
||||
const contractContext = castedRequire.context("@uma/core/build/contracts/", true, /\.json$/);
|
||||
|
||||
importedObjects = importAll(contractContext);
|
||||
} else {
|
||||
@@ -34,8 +48,8 @@ function getAllContracts() {
|
||||
const packageDir = path.dirname(require.resolve("@uma/core/package.json"));
|
||||
const contractsPath = path.join(packageDir, "build/contracts/");
|
||||
|
||||
const fileList = fs.readdirSync(contractsPath).filter((name) => name.match(/\.json$/));
|
||||
importedObjects = fileList.map((filename) => {
|
||||
const fileList = fs.readdirSync(contractsPath).filter((name: string) => name.match(/\.json$/));
|
||||
importedObjects = fileList.map((filename: string) => {
|
||||
const fileContents = fs.readFileSync(path.join(contractsPath, filename));
|
||||
return JSON.parse(fileContents);
|
||||
});
|
||||
@@ -44,7 +58,7 @@ function getAllContracts() {
|
||||
return importedObjects;
|
||||
}
|
||||
|
||||
function getAbiDecoder() {
|
||||
export function getAbiDecoder(): typeof abiDecoder {
|
||||
const contracts = getAllContracts();
|
||||
for (const contract of contracts) {
|
||||
abiDecoder.addABI(contract.abi);
|
||||
@@ -52,5 +66,3 @@ function getAbiDecoder() {
|
||||
|
||||
return abiDecoder;
|
||||
}
|
||||
|
||||
module.exports = { getAllContracts, getAbiDecoder };
|
||||
@@ -1,8 +1,15 @@
|
||||
const { getAbiDecoder } = require("./AbiUtils.js");
|
||||
import { getAbiDecoder, AbiDecoder } from "./AbiUtils";
|
||||
import type { BN } from "./types";
|
||||
|
||||
let abiDecoder;
|
||||
let abiDecoder: AbiDecoder;
|
||||
|
||||
function decodeTransaction(transaction) {
|
||||
interface Transaction {
|
||||
data?: string;
|
||||
to: string;
|
||||
value: string | BN;
|
||||
}
|
||||
|
||||
export function decodeTransaction(transaction: Transaction): string {
|
||||
let returnValue = "";
|
||||
|
||||
// Give to and value.
|
||||
@@ -33,17 +40,17 @@ function decodeTransaction(transaction) {
|
||||
|
||||
const adminPrefix = "Admin ";
|
||||
|
||||
function isAdminRequest(identifierUtf8) {
|
||||
export function isAdminRequest(identifierUtf8: string): boolean {
|
||||
return identifierUtf8.startsWith(adminPrefix);
|
||||
}
|
||||
|
||||
// Assumes that `identifierUtf8` is an admin request, i.e., `isAdminRequest()` returns true for it.
|
||||
function getAdminRequestId(identifierUtf8) {
|
||||
export function getAdminRequestId(identifierUtf8: string): number {
|
||||
return parseInt(identifierUtf8.slice(adminPrefix.length), 10);
|
||||
}
|
||||
|
||||
// Vote 1 for Yes, 0 for No. Any vote > 0 is technically a Yes, but the 1 is treated as the canonical yes.
|
||||
const translateAdminVote = (voteValue) => {
|
||||
export const translateAdminVote = (voteValue: string): string => {
|
||||
if (!voteValue) {
|
||||
return "No Vote";
|
||||
} else {
|
||||
@@ -61,5 +68,3 @@ const translateAdminVote = (voteValue) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { decodeTransaction, isAdminRequest, getAdminRequestId, translateAdminVote };
|
||||
@@ -1,47 +0,0 @@
|
||||
// The interface names that Finder.sol uses to refer to interfaces in the UMA system.
|
||||
const interfaceName = {
|
||||
FinancialContractsAdmin: "FinancialContractsAdmin",
|
||||
Oracle: "Oracle",
|
||||
Registry: "Registry",
|
||||
Store: "Store",
|
||||
IdentifierWhitelist: "IdentifierWhitelist",
|
||||
CollateralWhitelist: "CollateralWhitelist",
|
||||
AddressWhitelist: "CollateralWhitelist",
|
||||
OptimisticOracle: "OptimisticOracle",
|
||||
Bridge: "Bridge",
|
||||
GenericHandler: "GenericHandler",
|
||||
MockOracleAncillary: "Oracle",
|
||||
SinkOracle: "Oracle",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// These enforce the maximum number of transactions that can fit within one batch-commit and batch-reveal.
|
||||
// Based off the current gas limit from Etherscan over the last 6 months of 9950000,
|
||||
// the following maximum batchCommit, batchReveal and retrieveRewards are possible:
|
||||
// - batchCommit: 28 commits, 6654676 gas used
|
||||
// - batchReveal: 58 commits, 5828051 gas used
|
||||
// - retrieveRewards: 129 commits, 3344083 gas used
|
||||
// Practically, we set a safe upper bound of 25 batch commits & reveals and 100 retrievals.
|
||||
BATCH_MAX_COMMITS: 25,
|
||||
BATCH_MAX_REVEALS: 25,
|
||||
BATCH_MAX_RETRIEVALS: 100,
|
||||
|
||||
// maximum uint256 value: 2^256 - 1
|
||||
MAX_UINT_VAL: "115792089237316195423570985008687907853269984665640564039457584007913129639935",
|
||||
|
||||
// maximum allowance allowed by certain ERC20's like UNI: 2^96 - 1
|
||||
MAX_SAFE_ALLOWANCE: "79228162514264337593543950335",
|
||||
|
||||
// Max integer that can be safely stored in a vanilla js int.
|
||||
MAX_SAFE_JS_INT: 2147483647,
|
||||
|
||||
// 0x0 contract address
|
||||
ZERO_ADDRESS: "0x0000000000000000000000000000000000000000",
|
||||
|
||||
// Names of different interfaces in the Finder.
|
||||
interfaceName,
|
||||
|
||||
// Block number of first EMP created.
|
||||
// https://etherscan.io/tx/0x741ccbf0f9655b0b71e3842d788d58770bd3eb80c8f5bdf4fdec7cd74a776ea3
|
||||
UMA_FIRST_EMP_BLOCK: 10103723,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// The interface names that Finder.sol uses to refer to interfaces in the UMA system.
|
||||
export const interfaceName = {
|
||||
FinancialContractsAdmin: "FinancialContractsAdmin",
|
||||
Oracle: "Oracle",
|
||||
Registry: "Registry",
|
||||
Store: "Store",
|
||||
IdentifierWhitelist: "IdentifierWhitelist",
|
||||
CollateralWhitelist: "CollateralWhitelist",
|
||||
AddressWhitelist: "CollateralWhitelist",
|
||||
OptimisticOracle: "OptimisticOracle",
|
||||
Bridge: "Bridge",
|
||||
GenericHandler: "GenericHandler",
|
||||
MockOracleAncillary: "Oracle",
|
||||
SinkOracle: "Oracle",
|
||||
};
|
||||
|
||||
// These enforce the maximum number of transactions that can fit within one batch-commit and batch-reveal.
|
||||
// Based off the current gas limit from Etherscan over the last 6 months of 9950000,
|
||||
// the following maximum batchCommit, batchReveal and retrieveRewards are possible:
|
||||
// - batchCommit: 28 commits, 6654676 gas used
|
||||
// - batchReveal: 58 commits, 5828051 gas used
|
||||
// - retrieveRewards: 129 commits, 3344083 gas used
|
||||
// Practically, we set a safe upper bound of 25 batch commits & reveals and 100 retrievals.
|
||||
export const BATCH_MAX_COMMITS = 25;
|
||||
export const BATCH_MAX_REVEALS = 25;
|
||||
export const BATCH_MAX_RETRIEVALS = 100;
|
||||
|
||||
// maximum uint256 value: 2^256 - 1
|
||||
export const MAX_UINT_VAL = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
|
||||
|
||||
// maximum allowance allowed by certain ERC20's like UNI: 2^96 - 1
|
||||
export const MAX_SAFE_ALLOWANCE = "79228162514264337593543950335";
|
||||
|
||||
// Max integer that can be safely stored in a vanilla js int.
|
||||
export const MAX_SAFE_JS_INT = 2147483647;
|
||||
|
||||
// 0x0 contract address
|
||||
export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
||||
|
||||
// Block number of first EMP created.
|
||||
// https://etherscan.io/tx/0x741ccbf0f9655b0b71e3842d788d58770bd3eb80c8f5bdf4fdec7cd74a776ea3
|
||||
export const UMA_FIRST_EMP_BLOCK = 10103723;
|
||||
@@ -1,55 +0,0 @@
|
||||
const truffleContract = require("@truffle/contract");
|
||||
|
||||
/**
|
||||
* This is a hack to handle reverts for view/pure functions that don't actually revert on public networks.
|
||||
* See https://forum.openzeppelin.com/t/require-in-view-pure-functions-dont-revert-on-public-networks/1211 for more
|
||||
* info.
|
||||
* @param {Object} result Return value from calling a contract's view-only method.
|
||||
* @return null if the call reverted or the view method's result.
|
||||
*/
|
||||
const revertWrapper = (result) => {
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
let revertValue = "3963877391197344453575983046348115674221700746820753546331534351508065746944";
|
||||
if (result.toString() === revertValue) {
|
||||
return null;
|
||||
}
|
||||
const isObject = (obj) => {
|
||||
return obj === Object(obj);
|
||||
};
|
||||
if (isObject(result)) {
|
||||
// Iterate over the properties of the object and see if any match the revert value.
|
||||
for (let prop in result) {
|
||||
if (result[prop] && result[prop].toString() === revertValue) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* create a truffle contract from a json object, usually read in from an artifact.
|
||||
* @param {*} contractJsonObject json object representing a contract.
|
||||
* @param {Object} web3 instance. In unit tests this is globally accessable but when used in production needs injection.
|
||||
* @returns truffle contract instance
|
||||
*/
|
||||
const createContractObjectFromJson = (contractJsonObject, _web3 = web3) => {
|
||||
let truffleContractCreator = truffleContract(contractJsonObject);
|
||||
truffleContractCreator.setProvider(_web3.currentProvider);
|
||||
return truffleContractCreator;
|
||||
};
|
||||
/**
|
||||
* Helper to enable enables library linking on artifacts that were not compiled within this repo, such as artifacts
|
||||
* produced by an external project. Can also be useful if the artifact was compiled using ethers.
|
||||
* @param {object} artifact representing the compiled contract instance.
|
||||
* @param {string} libraryName to be found and replaced within the artifact.
|
||||
* @returns
|
||||
*/
|
||||
const replaceLibraryBindingReferenceInArtitifact = (artifact, libraryName) => {
|
||||
const artifactString = JSON.stringify(artifact);
|
||||
return JSON.parse(artifactString.replace(/\$.*\$/g, libraryName));
|
||||
};
|
||||
|
||||
module.exports = { revertWrapper, createContractObjectFromJson, replaceLibraryBindingReferenceInArtitifact };
|
||||
@@ -0,0 +1,81 @@
|
||||
// The types in this package are broken, so we have to require it.
|
||||
const contractConstructor_ = require("@truffle/contract");
|
||||
import type truffleContract_ from "@truffle/contract";
|
||||
import type { BN } from "./types";
|
||||
import Web3 from "web3";
|
||||
import type { provider as Provider } from "web3-core";
|
||||
|
||||
// Truffle library types aren't specified correctly. Cast and modify to correct for this.
|
||||
export interface TruffleInstance {
|
||||
[prop: string]: any;
|
||||
}
|
||||
export interface TruffleContract extends truffleContract_.Contract {
|
||||
setProvider: (provider: Provider) => void;
|
||||
at: (address: string) => Promise<TruffleInstance>;
|
||||
deployed: () => Promise<TruffleInstance>;
|
||||
new: (...args: any[]) => Promise<TruffleInstance>;
|
||||
link: (arg: any) => TruffleContract;
|
||||
detectNetwork: () => Promise<void>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const truffleContract = contractConstructor_ as (artifact: any) => TruffleContract;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type CallResult = string | BN | { [key: string]: any };
|
||||
|
||||
function isBN(input: CallResult): input is BN {
|
||||
return input?.constructor?.name === "BN";
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hack to handle reverts for view/pure functions that don't actually revert on public networks.
|
||||
* See https://forum.openzeppelin.com/t/require-in-view-pure-functions-dont-revert-on-public-networks/1211 for more
|
||||
* info.
|
||||
* @param {Object} result Return value from calling a contract's view-only method.
|
||||
* @return null if the call reverted or the view method's result.
|
||||
*/
|
||||
export const revertWrapper = (result: CallResult): null | CallResult => {
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const revertValue = "3963877391197344453575983046348115674221700746820753546331534351508065746944";
|
||||
if (result.toString() === revertValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof result !== "string" && !isBN(result)) {
|
||||
// Iterate over the properties of the object and see if any match the revert value.
|
||||
for (const prop in result) {
|
||||
if (!(prop in result) && result[prop].toString() === revertValue) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* create a truffle contract from a json object, usually read in from an artifact.
|
||||
* @param {*} contractJsonObject json object representing a contract.
|
||||
* @param {Object} web3 instance. In unit tests this is globally accessable but when used in production needs injection.
|
||||
* @returns truffle contract instance
|
||||
*/
|
||||
export const createContractObjectFromJson = (
|
||||
contractJsonObject: { [key: string]: any },
|
||||
_web3 = ((global as unknown) as { web3: Web3 }).web3
|
||||
): TruffleContract => {
|
||||
const truffleContractCreator = truffleContract(contractJsonObject);
|
||||
truffleContractCreator.setProvider(_web3.currentProvider);
|
||||
return truffleContractCreator;
|
||||
};
|
||||
/**
|
||||
* Helper to enable enables library linking on artifacts that were not compiled within this repo, such as artifacts
|
||||
* produced by an external project. Can also be useful if the artifact was compiled using ethers.
|
||||
* @param {object} artifact representing the compiled contract instance.
|
||||
* @param {string} libraryName to be found and replaced within the artifact.
|
||||
*/
|
||||
export const replaceLibraryBindingReferenceInArtitifact = <T>(artifact: T, libraryName: string): T => {
|
||||
const artifactString = JSON.stringify(artifact);
|
||||
return JSON.parse(artifactString.replace(/\$.*\$/g, libraryName));
|
||||
};
|
||||
@@ -1,45 +1,63 @@
|
||||
const EthCrypto = require("eth-crypto");
|
||||
import EthCrypto from "eth-crypto";
|
||||
import type Web3 from "web3";
|
||||
import assert from "assert";
|
||||
|
||||
interface KeyPair {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
// Encrypts a message using an ethereum public key. To decrypt messages that are encrypted with this method, use
|
||||
// decryptMessage().
|
||||
async function encryptMessage(publicKey, message) {
|
||||
export async function encryptMessage(publicKey: string, message: string): Promise<string> {
|
||||
// substr(2) removes the web3 friendly "0x" from the public key.
|
||||
const encryptedMessageObject = await EthCrypto.encryptWithPublicKey(publicKey.substr(2), message);
|
||||
return "0x" + EthCrypto.cipher.stringify(encryptedMessageObject);
|
||||
}
|
||||
|
||||
// Converts an ethereum public key to the corresponding address.
|
||||
function addressFromPublicKey(publicKey) {
|
||||
export function addressFromPublicKey(publicKey: string): string {
|
||||
// substr(2) just removes the web3 friendly "0x" from the public key.
|
||||
return EthCrypto.publicKey.toAddress(publicKey.substr(2));
|
||||
}
|
||||
|
||||
// Recovers a public key from a private key.
|
||||
function recoverPublicKey(privateKey) {
|
||||
export function recoverPublicKey(privateKey: string): string {
|
||||
// The "0x" is added to make the public key web3 friendly.
|
||||
return "0x" + EthCrypto.publicKeyByPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
// Decrypts a message that was encrypted using encryptMessage().
|
||||
async function decryptMessage(privKey, encryptedMessage) {
|
||||
export async function decryptMessage(privKey: string, encryptedMessage: string): Promise<string> {
|
||||
// substr(2) just removes the 0x at the beginning. parse() reverses the stringify() in encryptMessage().
|
||||
const encryptedMessageObject = EthCrypto.cipher.parse(encryptedMessage.substr(2));
|
||||
return await EthCrypto.decryptWithPrivateKey(privKey, encryptedMessageObject);
|
||||
}
|
||||
|
||||
// Derives a private key from a signature.
|
||||
async function deriveKeyPair(web3, signature) {
|
||||
const privateKey = web3.utils.soliditySha3(signature).substr(2);
|
||||
export async function deriveKeyPair(web3: Web3, signature: string): Promise<KeyPair> {
|
||||
const hashOutput = web3.utils.soliditySha3(signature);
|
||||
assert(hashOutput, "hash returned null");
|
||||
const privateKey = hashOutput.substr(2);
|
||||
const publicKey = recoverPublicKey(privateKey);
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
|
||||
// The methods to get signatures in MetaMask and Truffle are different and return slightly different results.
|
||||
async function getMessageSignatureMetamask(web3, messageToSign, signingAccount) {
|
||||
return await web3.eth.personal.sign(messageToSign, signingAccount);
|
||||
export async function getMessageSignatureMetamask(
|
||||
web3: Web3,
|
||||
messageToSign: string,
|
||||
signingAccount: string
|
||||
): Promise<string> {
|
||||
// Note: web3 types seem to erroneously assume that a third argument is required, but it is not, so pass undefined and cast to string.
|
||||
return await web3.eth.personal.sign(messageToSign, signingAccount, (undefined as unknown) as string);
|
||||
}
|
||||
|
||||
async function getMessageSignatureTruffle(web3, messageToSign, signingAccount) {
|
||||
export async function getMessageSignatureTruffle(
|
||||
web3: Web3,
|
||||
messageToSign: string,
|
||||
signingAccount: string
|
||||
): Promise<string> {
|
||||
const signature = await web3.eth.sign(messageToSign, signingAccount);
|
||||
// The 65 byte signature consists of r (first 32 bytes), s (next 32 bytes), and v (final byte). 65 bytes is
|
||||
// represented as 130 hex digits and the initial "0x". In order to produce a consistent signature with Metamask, we
|
||||
@@ -59,19 +77,28 @@ async function getMessageSignatureTruffle(web3, messageToSign, signingAccount) {
|
||||
// messages.
|
||||
|
||||
// Derive a private key, that works *only* on Metamask.
|
||||
async function deriveKeyPairFromSignatureMetamask(web3, messageToSign, signingAccount) {
|
||||
export async function deriveKeyPairFromSignatureMetamask(
|
||||
web3: Web3,
|
||||
messageToSign: string,
|
||||
signingAccount: string
|
||||
): Promise<KeyPair> {
|
||||
return deriveKeyPair(web3, await getMessageSignatureMetamask(web3, messageToSign, signingAccount));
|
||||
}
|
||||
|
||||
// Derive a private key, that works *only* with Truffle.
|
||||
async function deriveKeyPairFromSignatureTruffle(web3, messageToSign, signingAccount) {
|
||||
export async function deriveKeyPairFromSignatureTruffle(
|
||||
web3: Web3,
|
||||
messageToSign: string,
|
||||
signingAccount: string
|
||||
): Promise<KeyPair> {
|
||||
return deriveKeyPair(web3, await getMessageSignatureTruffle(web3, messageToSign, signingAccount));
|
||||
}
|
||||
|
||||
// Signs a message in a way where it can be verified onchain by the openzeppelin ECDSA library.
|
||||
async function signMessage(web3, message, account) {
|
||||
export async function signMessage(web3: Web3, message: string, account: string): Promise<string> {
|
||||
// Must hash the inner message because Solidity requires a fixed length message to verify a signature.
|
||||
const innerMessageHash = await web3.utils.soliditySha3(message);
|
||||
assert(innerMessageHash, "innerMessageHash is null");
|
||||
|
||||
// Construct a signature that will be accepted by openzeppelin.
|
||||
// See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/1e584e495782ebdb5096fe65037d99dae1cbe940/contracts/cryptography/ECDSA.sol#L53
|
||||
@@ -86,15 +113,3 @@ async function signMessage(web3, message, account) {
|
||||
}
|
||||
return rs + v;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
encryptMessage,
|
||||
addressFromPublicKey,
|
||||
decryptMessage,
|
||||
recoverPublicKey,
|
||||
deriveKeyPairFromSignatureTruffle,
|
||||
deriveKeyPairFromSignatureMetamask,
|
||||
getMessageSignatureTruffle,
|
||||
getMessageSignatureMetamask,
|
||||
signMessage,
|
||||
};
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { BN } from "./types";
|
||||
import type Web3 from "web3";
|
||||
|
||||
/**
|
||||
* Get the collateralization ratio for an EMP position.
|
||||
* Note: all inputs and outputs are expressed as fixed-point (scaled by 1e18) BNs.
|
||||
@@ -7,7 +10,7 @@
|
||||
* @param {BN} number of tokens that have been borrowed/minted against the position
|
||||
* @return {BN} collateralization ratio.
|
||||
*/
|
||||
function computeCollateralizationRatio(web3, tokenPrice, collateral, tokensOutstanding) {
|
||||
export function computeCollateralizationRatio(web3: Web3, tokenPrice: BN, collateral: BN, tokensOutstanding: BN): BN {
|
||||
const { toWei, toBN } = web3.utils;
|
||||
const fixedPointScalingFactor = toBN(toWei("1"));
|
||||
if (tokensOutstanding.isZero() || tokenPrice.isZero()) {
|
||||
@@ -15,5 +18,3 @@ function computeCollateralizationRatio(web3, tokenPrice, collateral, tokensOutst
|
||||
}
|
||||
return collateral.mul(fixedPointScalingFactor).mul(fixedPointScalingFactor).div(tokensOutstanding).div(tokenPrice);
|
||||
}
|
||||
|
||||
module.exports = { computeCollateralizationRatio };
|
||||
@@ -1,19 +1,35 @@
|
||||
const web3 = require("web3");
|
||||
import Web3 from "web3";
|
||||
import type { BN } from "./types";
|
||||
|
||||
interface TopicHashRequest {
|
||||
identifier: string;
|
||||
time: string | BN;
|
||||
}
|
||||
|
||||
// Web3's soliditySha3 will attempt to auto-detect the type of given input parameters,
|
||||
// but this won't produce expected behavior for certain types such as `bytes32` or `address`.
|
||||
// Therefore, these helper methods will explicitly set types.
|
||||
|
||||
function computeTopicHash(request, roundId) {
|
||||
return web3.utils.soliditySha3(
|
||||
export function computeTopicHash(request: TopicHashRequest, roundId: number | string): string {
|
||||
const hash = Web3.utils.soliditySha3(
|
||||
{ t: "bytes32", v: request.identifier },
|
||||
{ t: "uint", v: request.time },
|
||||
{ t: "uint", v: roundId }
|
||||
);
|
||||
if (hash === null) throw new Error("Returned null hash.");
|
||||
return hash;
|
||||
}
|
||||
|
||||
function computeVoteHash(request) {
|
||||
return web3.utils.soliditySha3(
|
||||
interface VoteHashRequest {
|
||||
price: string | BN;
|
||||
salt: string | BN;
|
||||
account: string;
|
||||
time: string | BN | number;
|
||||
roundId: string | BN | number;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export function computeVoteHash(request: VoteHashRequest): string {
|
||||
const hash = Web3.utils.soliditySha3(
|
||||
{ t: "int", v: request.price },
|
||||
{ t: "int", v: request.salt },
|
||||
{ t: "address", v: request.account },
|
||||
@@ -22,10 +38,16 @@ function computeVoteHash(request) {
|
||||
{ t: "uint", v: request.roundId },
|
||||
{ t: "bytes32", v: request.identifier }
|
||||
);
|
||||
if (hash === null) throw new Error("Returned null hash.");
|
||||
return hash;
|
||||
}
|
||||
|
||||
function computeVoteHashAncillary(request) {
|
||||
return web3.utils.soliditySha3(
|
||||
interface VoteHashAncillaryRequest extends VoteHashRequest {
|
||||
ancillaryData: string;
|
||||
}
|
||||
|
||||
export function computeVoteHashAncillary(request: VoteHashAncillaryRequest): string {
|
||||
const hash = Web3.utils.soliditySha3(
|
||||
{ t: "int", v: request.price },
|
||||
{ t: "int", v: request.salt },
|
||||
{ t: "address", v: request.account },
|
||||
@@ -34,11 +56,11 @@ function computeVoteHashAncillary(request) {
|
||||
{ t: "uint", v: request.roundId },
|
||||
{ t: "bytes32", v: request.identifier }
|
||||
);
|
||||
if (hash === null) throw new Error("Returned null hash.");
|
||||
return hash;
|
||||
}
|
||||
|
||||
function getKeyGenMessage(roundId) {
|
||||
export function getKeyGenMessage(roundId: number | string): string {
|
||||
// TODO: discuss dApp tradeoffs for changing this to a per-topic hash keypair.
|
||||
return `UMA Protocol one time key for round: ${roundId.toString()}`;
|
||||
}
|
||||
|
||||
module.exports = { computeTopicHash, computeVoteHash, computeVoteHashAncillary, getKeyGenMessage };
|
||||
@@ -1,64 +0,0 @@
|
||||
// Corresponds to Registry.Roles.
|
||||
const RegistryRolesEnum = { OWNER: "0", CONTRACT_CREATOR: "1" };
|
||||
|
||||
const TokenRolesEnum = { OWNER: "0", MINTER: "1", BURNER: "3" };
|
||||
|
||||
// Corresponds to VoteTiming.Phase.
|
||||
const VotePhasesEnum = { COMMIT: "0", REVEAL: "1" };
|
||||
|
||||
// States for an EMP's Liquidation to be in.
|
||||
const LiquidationStatesEnum = {
|
||||
UNINITIALIZED: "0",
|
||||
PRE_DISPUTE: "1",
|
||||
PENDING_DISPUTE: "2",
|
||||
DISPUTE_SUCCEEDED: "3",
|
||||
DISPUTE_FAILED: "4",
|
||||
};
|
||||
|
||||
// Maps the `liquidationStatus` property in the `LiquidationWithdrawn` event to human readable statuses.
|
||||
// Note that these are status translations AFTER a withdrawLiquidation method is called
|
||||
const PostWithdrawLiquidationRewardsStatusTranslations = {
|
||||
0: "Uninitialized",
|
||||
1: "NotDisputed",
|
||||
2: "Disputed",
|
||||
3: "DisputeSucceeded",
|
||||
4: "DisputeFailed",
|
||||
};
|
||||
|
||||
// States for an EMP's Position to be in.
|
||||
const PositionStatesEnum = { OPEN: "0", EXPIRED_PRICE_REQUESTED: "1", EXPIRED_PRICE_RECEIVED: "2" };
|
||||
|
||||
const PriceRequestStatusEnum = { NOT_REQUESTED: "0", ACTIVE: "1", RESOLVED: "2", FUTURE: "3" };
|
||||
|
||||
const OptimisticOracleRequestStatesEnum = {
|
||||
INVALID: "0",
|
||||
REQUESTED: "1",
|
||||
PROPOSED: "2",
|
||||
EXPIRED: "3",
|
||||
DISPUTED: "4",
|
||||
RESOLVED: "5",
|
||||
SETTLED: "6",
|
||||
};
|
||||
|
||||
const InsuredBridgeDepositStateEnum = {
|
||||
UNINITIALIZED: "0",
|
||||
PENDING_SLOW: "1",
|
||||
PENDING_INSTANT: "2",
|
||||
FINALIZED_SLOW: "3",
|
||||
FINALIZED_INSTANT: "4",
|
||||
};
|
||||
|
||||
const InsuredBridgeDepositTypeEnum = { SLOW: "0", INSTANT: "1" };
|
||||
|
||||
module.exports = {
|
||||
RegistryRolesEnum,
|
||||
TokenRolesEnum,
|
||||
VotePhasesEnum,
|
||||
LiquidationStatesEnum,
|
||||
PostWithdrawLiquidationRewardsStatusTranslations,
|
||||
PositionStatesEnum,
|
||||
PriceRequestStatusEnum,
|
||||
OptimisticOracleRequestStatesEnum,
|
||||
InsuredBridgeDepositStateEnum,
|
||||
InsuredBridgeDepositTypeEnum,
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
// Corresponds to Registry.Roles.
|
||||
export const RegistryRolesEnum = { OWNER: "0", CONTRACT_CREATOR: "1" };
|
||||
|
||||
export const TokenRolesEnum = { OWNER: "0", MINTER: "1", BURNER: "3" };
|
||||
|
||||
// Corresponds to VoteTiming.Phase.
|
||||
export const VotePhasesEnum = { COMMIT: "0", REVEAL: "1" };
|
||||
|
||||
// States for an EMP's Liquidation to be in.
|
||||
export const LiquidationStatesEnum = {
|
||||
UNINITIALIZED: "0",
|
||||
PRE_DISPUTE: "1",
|
||||
PENDING_DISPUTE: "2",
|
||||
DISPUTE_SUCCEEDED: "3",
|
||||
DISPUTE_FAILED: "4",
|
||||
};
|
||||
|
||||
// Maps the `liquidationStatus` property in the `LiquidationWithdrawn` event to human readable statuses.
|
||||
// Note that these are status translations AFTER a withdrawLiquidation method is called
|
||||
export const PostWithdrawLiquidationRewardsStatusTranslations = {
|
||||
0: "Uninitialized",
|
||||
1: "NotDisputed",
|
||||
2: "Disputed",
|
||||
3: "DisputeSucceeded",
|
||||
4: "DisputeFailed",
|
||||
};
|
||||
|
||||
// States for an EMP's Position to be in.
|
||||
export const PositionStatesEnum = { OPEN: "0", EXPIRED_PRICE_REQUESTED: "1", EXPIRED_PRICE_RECEIVED: "2" };
|
||||
|
||||
export const PriceRequestStatusEnum = { NOT_REQUESTED: "0", ACTIVE: "1", RESOLVED: "2", FUTURE: "3" };
|
||||
|
||||
export const OptimisticOracleRequestStatesEnum = {
|
||||
INVALID: "0",
|
||||
REQUESTED: "1",
|
||||
PROPOSED: "2",
|
||||
EXPIRED: "3",
|
||||
DISPUTED: "4",
|
||||
RESOLVED: "5",
|
||||
SETTLED: "6",
|
||||
};
|
||||
|
||||
export const InsuredBridgeDepositStateEnum = {
|
||||
UNINITIALIZED: "0",
|
||||
PENDING_SLOW: "1",
|
||||
PENDING_INSTANT: "2",
|
||||
FINALIZED_SLOW: "3",
|
||||
FINALIZED_INSTANT: "4",
|
||||
};
|
||||
|
||||
export const InsuredBridgeDepositTypeEnum = { SLOW: "0", INSTANT: "1" };
|
||||
@@ -1,11 +1,10 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type { Artifact } from "hardhat/types";
|
||||
|
||||
function getAllFilesInPath(dirPath, arrayOfFiles = []) {
|
||||
export function getAllFilesInPath(dirPath: string, arrayOfFiles: string[] = []): string[] {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
arrayOfFiles = arrayOfFiles || [];
|
||||
|
||||
files.forEach((file) => {
|
||||
if (fs.statSync(dirPath + "/" + file).isDirectory())
|
||||
arrayOfFiles = getAllFilesInPath(dirPath + "/" + file, arrayOfFiles);
|
||||
@@ -15,7 +14,7 @@ function getAllFilesInPath(dirPath, arrayOfFiles = []) {
|
||||
return arrayOfFiles;
|
||||
}
|
||||
|
||||
function findArtifactFromPath(contractName, artifactsPath) {
|
||||
export function findArtifactFromPath(contractName: string, artifactsPath: string): Artifact {
|
||||
const allArtifactsPaths = getAllFilesInPath(artifactsPath);
|
||||
const desiredArtifactPaths = allArtifactsPaths.filter((a) => a.endsWith(`/${contractName}.json`));
|
||||
|
||||
@@ -23,5 +22,3 @@ function findArtifactFromPath(contractName, artifactsPath) {
|
||||
throw new Error(`Couldn't find desired artifact or found too many for ${contractName}`);
|
||||
return JSON.parse(fs.readFileSync(desiredArtifactPaths[0], "utf-8"));
|
||||
}
|
||||
|
||||
module.exports = { getAllFilesInPath, findArtifactFromPath };
|
||||
@@ -1,9 +1,11 @@
|
||||
const { PublicNetworks: networkUtils } = require("./PublicNetworks");
|
||||
import { PublicNetworks as networkUtils } from "./PublicNetworks";
|
||||
import Web3 from "web3";
|
||||
import type { BN } from "./types";
|
||||
|
||||
const BigNumber = require("bignumber.js");
|
||||
const moment = require("moment");
|
||||
const assert = require("assert");
|
||||
const { formatFixed, parseFixed } = require("@ethersproject/bignumber");
|
||||
import BigNumber from "bignumber.js";
|
||||
import moment from "moment";
|
||||
import assert from "assert";
|
||||
export { formatFixed, parseFixed } from "@ethersproject/bignumber";
|
||||
|
||||
// Apply settings to BigNumber.js library.
|
||||
// Note: ROUNDING_MODE is set to round ceiling so we send at least enough collateral to create the requested tokens.
|
||||
@@ -13,47 +15,53 @@ const { formatFixed, parseFixed } = require("@ethersproject/bignumber");
|
||||
BigNumber.set({ ROUNDING_MODE: 2, RANGE: 500, EXPONENTIAL_AT: 500 });
|
||||
|
||||
// Given a timestamp in seconds, returns the date in the format: "MM/DD/YYYY"
|
||||
const formatDateShort = (timestampInSeconds) => {
|
||||
const date = moment.unix(parseInt(Number(timestampInSeconds)));
|
||||
export const formatDateShort = (timestampInSeconds: number): string => {
|
||||
const date = moment.unix(Math.floor(timestampInSeconds));
|
||||
return date.format("MM/DD/YYYY");
|
||||
};
|
||||
|
||||
const formatDate = (timestampInSeconds, web3) => {
|
||||
return new Date(parseInt(web3.utils.toBN(timestampInSeconds).muln(1000).toString(), 10)).toString();
|
||||
export const formatDate = (timestampInSeconds: number): string => {
|
||||
return new Date(Math.floor(timestampInSeconds * 1000)).toString();
|
||||
};
|
||||
|
||||
const formatHours = (seconds, decimals = 2) => {
|
||||
export const formatHours = (seconds: number, decimals = 2): string => {
|
||||
// 3600 seconds in an hour.
|
||||
return (seconds / 3600).toFixed(decimals);
|
||||
};
|
||||
|
||||
// formatWei converts a string or BN instance from Wei to Ether, e.g., 1e19 -> 10.
|
||||
const formatWei = (num, web3) => {
|
||||
export const formatWei = (num: string | BN): string => {
|
||||
// Web3's `fromWei` function doesn't work on BN objects in minified mode (e.g.,
|
||||
// `web3.utils.isBN(web3.utils.fromBN("5"))` is false), so we use a workaround where we always pass in strings.
|
||||
// See https://github.com/ethereum/web3.js/issues/1777.
|
||||
return web3.utils.fromWei(num.toString());
|
||||
return Web3.utils.fromWei(num.toString());
|
||||
};
|
||||
|
||||
// Formats the input to round to decimalPlaces number of decimals if the number has a magnitude larger than 1 and fixes
|
||||
// precision to minPrecision if the number has a magnitude less than 1.
|
||||
const formatWithMaxDecimals = (num, decimalPlaces, minPrecision, roundUp, showSign) => {
|
||||
export const formatWithMaxDecimals = (
|
||||
num: number | string,
|
||||
decimalPlaces: number,
|
||||
minPrecision: number,
|
||||
roundUp: boolean,
|
||||
showSign: boolean
|
||||
): string => {
|
||||
if (roundUp) {
|
||||
BigNumber.set({ ROUNDING_MODE: BigNumber.ROUND_UP });
|
||||
} else {
|
||||
BigNumber.set({ ROUNDING_MODE: BigNumber.ROUND_DOWN });
|
||||
}
|
||||
|
||||
const fullPrecisionFloat = BigNumber(num);
|
||||
const fullPrecisionFloat = new BigNumber(num);
|
||||
const positiveSign = showSign && fullPrecisionFloat.gt(0) ? "+" : "";
|
||||
let fixedPrecisionFloat;
|
||||
// Convert back to BN to truncate any trailing 0s that the toFixed() output would print. If the number is equal to or larger than
|
||||
// 1 then truncate to `decimalPlaces` number of decimal places. EG 999.999 -> 999.99 with decimalPlaces=2 If the number
|
||||
// is less than 1 then truncate to minPrecision precision. EG: 0.0022183471 -> 0.002218 with minPrecision=4
|
||||
if (fullPrecisionFloat.abs().gte(BigNumber(1))) {
|
||||
fixedPrecisionFloat = BigNumber(fullPrecisionFloat).toFixed(decimalPlaces).toString();
|
||||
if (fullPrecisionFloat.abs().gte(new BigNumber(1))) {
|
||||
fixedPrecisionFloat = new BigNumber(fullPrecisionFloat).toFixed(decimalPlaces).toString();
|
||||
} else {
|
||||
fixedPrecisionFloat = BigNumber(fullPrecisionFloat).toPrecision(minPrecision).toString();
|
||||
fixedPrecisionFloat = new BigNumber(fullPrecisionFloat).toPrecision(minPrecision).toString();
|
||||
}
|
||||
// This puts commas in the thousands places, but only before the decimal point.
|
||||
const fixedPrecisionFloatParts = fixedPrecisionFloat.split(".");
|
||||
@@ -61,10 +69,16 @@ const formatWithMaxDecimals = (num, decimalPlaces, minPrecision, roundUp, showSi
|
||||
return positiveSign + fixedPrecisionFloatParts.join(".");
|
||||
};
|
||||
|
||||
const createFormatFunction = (web3, numDisplayedDecimals, minDisplayedPrecision, showSign = false, decimals = 18) => {
|
||||
return (valInWei) =>
|
||||
export const createFormatFunction = (
|
||||
_web3: Web3 | undefined, // unused
|
||||
numDisplayedDecimals: number,
|
||||
minDisplayedPrecision: number,
|
||||
showSign = false,
|
||||
decimals = 18
|
||||
) => {
|
||||
return (valInWei: string | BN): string =>
|
||||
formatWithMaxDecimals(
|
||||
formatWei(ConvertDecimals(decimals, 18, web3)(valInWei), web3),
|
||||
formatWei(ConvertDecimals(decimals, 18)(valInWei)),
|
||||
numDisplayedDecimals,
|
||||
minDisplayedPrecision,
|
||||
false,
|
||||
@@ -72,8 +86,9 @@ const createFormatFunction = (web3, numDisplayedDecimals, minDisplayedPrecision,
|
||||
);
|
||||
};
|
||||
|
||||
type NetworkId = keyof typeof networkUtils;
|
||||
// Generate an etherscan link prefix. If a networkId is provided then the URL will point to this network. Else, assume mainnet.
|
||||
function createEtherscanLinkFromtx(networkId) {
|
||||
export function createEtherscanLinkFromtx(networkId: NetworkId): string {
|
||||
// Construct etherscan link based on network
|
||||
let url;
|
||||
if (networkUtils[networkId]) {
|
||||
@@ -88,22 +103,23 @@ function createEtherscanLinkFromtx(networkId) {
|
||||
|
||||
// Convert either an address or transaction to a shorter version.
|
||||
// 0x772871a444c6e4e9903d8533a5a13101b74037158123e6709470f0afbf6e7d94 -> 0x7787...7d94
|
||||
function createShortHexString(hex) {
|
||||
export function createShortHexString(hex: string): string {
|
||||
return hex.substring(0, 5) + "..." + hex.substring(hex.length - 6, hex.length - 1);
|
||||
}
|
||||
|
||||
// Take in either a transaction or an account and generate an etherscan link for the corresponding
|
||||
// network formatted in markdown.
|
||||
function createEtherscanLinkMarkdown(hex, networkId = 1) {
|
||||
export function createEtherscanLinkMarkdown(hex: string, networkId: NetworkId = 1): string | null {
|
||||
if (hex.substring(0, 2) != "0x") return null;
|
||||
let shortURLString = createShortHexString(hex);
|
||||
const shortURLString = createShortHexString(hex);
|
||||
// Transaction hash
|
||||
if (hex.length == 66) return `<${createEtherscanLinkFromtx(networkId)}tx/${hex}|${shortURLString}>`;
|
||||
// Account
|
||||
else if (hex.length == 42) return `<${createEtherscanLinkFromtx(networkId)}address/${hex}|${shortURLString}>`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function addSign(number) {
|
||||
export function addSign(number: string): string {
|
||||
if (Number(number) > 0) {
|
||||
return `+${number}`;
|
||||
} else {
|
||||
@@ -120,34 +136,17 @@ function addSign(number) {
|
||||
// toDecimals: number - decimal value to convert to
|
||||
// web3: web3 object to get a big number function.
|
||||
// return => (amount:string)=>BN
|
||||
const ConvertDecimals = (fromDecimals, toDecimals, web3) => {
|
||||
export const ConvertDecimals = (fromDecimals: number, toDecimals: number): ((amountIn: string | number | BN) => BN) => {
|
||||
assert(fromDecimals >= 0, "requires fromDecimals as an integer >= 0");
|
||||
assert(toDecimals >= 0, "requires toDecimals as an integer >= 0");
|
||||
assert(web3, "requires web3 instance");
|
||||
// amount: string, BN, number - integer amount in fromDecimals smallest unit that want to convert toDecimals
|
||||
// returns: BN with toDecimals in smallest unit
|
||||
return (amount) => {
|
||||
amount = web3.utils.toBN(amount);
|
||||
return (amountIn: string | number | BN) => {
|
||||
const amount = Web3.utils.toBN(amountIn.toString());
|
||||
if (amount.isZero()) return amount;
|
||||
const diff = fromDecimals - toDecimals;
|
||||
if (diff == 0) return amount;
|
||||
if (diff > 0) return amount.div(web3.utils.toBN("10").pow(web3.utils.toBN(diff.toString())));
|
||||
return amount.mul(web3.utils.toBN("10").pow(web3.utils.toBN((-1 * diff).toString())));
|
||||
if (diff > 0) return amount.div(Web3.utils.toBN("10").pow(Web3.utils.toBN(diff.toString())));
|
||||
return amount.mul(Web3.utils.toBN("10").pow(Web3.utils.toBN((-1 * diff).toString())));
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
formatDateShort,
|
||||
formatDate,
|
||||
formatHours,
|
||||
formatWei,
|
||||
formatWithMaxDecimals,
|
||||
createFormatFunction,
|
||||
createEtherscanLinkFromtx,
|
||||
createShortHexString,
|
||||
createEtherscanLinkMarkdown,
|
||||
addSign,
|
||||
formatFixed,
|
||||
parseFixed,
|
||||
ConvertDecimals,
|
||||
};
|
||||
@@ -1,7 +1,12 @@
|
||||
import { HardhatConfig } from "hardhat/types";
|
||||
|
||||
const { getNodeUrl, mnemonic } = require("./TruffleConfig");
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function getHardhatConfig(configOverrides, workingDir = "./", includeTruffle = true) {
|
||||
export function getHardhatConfig(
|
||||
configOverrides: any,
|
||||
_workingDir = "./",
|
||||
includeTruffle = true
|
||||
): Partial<HardhatConfig> {
|
||||
// Hardhat plugins. These are imported inside `getHardhatConfig` so that other packages importing this function
|
||||
// get access to the plugins as well.
|
||||
if (includeTruffle) require("@nomiclabs/hardhat-truffle5");
|
||||
@@ -32,7 +37,7 @@ function getHardhatConfig(configOverrides, workingDir = "./", includeTruffle = t
|
||||
// Some tests should not be tested using hardhat. Define all tests that end with *e2e.js to be ignored.
|
||||
const testBlacklist = [".e2e.js"];
|
||||
|
||||
const defaultConfig = {
|
||||
const defaultConfig = ({
|
||||
solidity: {
|
||||
compilers: [
|
||||
{ version: solcVersion, settings: { optimizer: { enabled: true, runs: 1000000 } } },
|
||||
@@ -88,7 +93,7 @@ function getHardhatConfig(configOverrides, workingDir = "./", includeTruffle = t
|
||||
apiKey: process.env.ETHERSCAN_API_KEY,
|
||||
},
|
||||
namedAccounts: { deployer: 0 },
|
||||
};
|
||||
} as unknown) as HardhatConfig; // Cast to allow extra properties.
|
||||
|
||||
return { ...defaultConfig, ...configOverrides };
|
||||
}
|
||||
@@ -96,10 +101,12 @@ function getHardhatConfig(configOverrides, workingDir = "./", includeTruffle = t
|
||||
// Helper method to let the user of HardhatConfig assign a global address which is then accessible from the @uma/core
|
||||
// getAddressTest method. This enables hardhat to be used in tests like the main index.js entry tests in the liquidator
|
||||
// disputer and monitor bots. In future, this should be refactored to use https://github.com/wighawag/hardhat-deploy
|
||||
function addGlobalHardhatTestingAddress(contractName, address) {
|
||||
if (!global.hardhatTestingAddresses) {
|
||||
global.hardhatTestingAddresses = {};
|
||||
export function addGlobalHardhatTestingAddress(contractName: string, address: string): void {
|
||||
const castedGlobal = (global as unknown) as {
|
||||
hardhatTestingAddresses: undefined | { [contractName: string]: string };
|
||||
};
|
||||
if (!castedGlobal.hardhatTestingAddresses) {
|
||||
castedGlobal.hardhatTestingAddresses = {};
|
||||
}
|
||||
global.hardhatTestingAddresses[contractName] = address;
|
||||
castedGlobal.hardhatTestingAddresses[contractName] = address;
|
||||
}
|
||||
module.exports = { getHardhatConfig, addGlobalHardhatTestingAddress };
|
||||
+18
-16
@@ -1,35 +1,37 @@
|
||||
const MetaMaskConnector = require("node-metamask");
|
||||
const argv = require("minimist")(process.argv.slice());
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import MetaMaskConnector from "node-metamask";
|
||||
import minimist from "minimist";
|
||||
import type { AbstractProvider } from "web3-core";
|
||||
const argv = minimist(process.argv.slice());
|
||||
|
||||
type MetamaskProvider = ReturnType<MetaMaskConnector["getProvider"]>;
|
||||
|
||||
// Wraps the MetaMask Connector enabling truffle to init a web3 provider and continue truffle execution until the
|
||||
// MetaMask connection has been established. This calls metaMask asynchronously while returning a provider synchronously.
|
||||
class MetaMaskTruffleProvider {
|
||||
export class MetaMaskTruffleProvider implements AbstractProvider {
|
||||
public wrappedProvider: MetamaskProvider | null;
|
||||
public wrappedProviderPromise: Promise<MetamaskProvider>;
|
||||
constructor() {
|
||||
this.wrappedProvider = null;
|
||||
this.wrappedProviderPromise = this.getOrConstructWrappedProvider();
|
||||
}
|
||||
|
||||
// Passes the call through, by attaching a callback to the wrapper provider promise.
|
||||
sendAsync(...all) {
|
||||
sendAsync(...all: Parameters<AbstractProvider["sendAsync"]>): void {
|
||||
this.wrappedProviderPromise.then((wrappedProvider) => {
|
||||
wrappedProvider.sendAsync(...all);
|
||||
});
|
||||
}
|
||||
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `constructWrappedProvider`.
|
||||
send(...all) {
|
||||
this.wrappedProviderPromise.then((wrappedProvider) => {
|
||||
send(...all: Parameters<NonNullable<AbstractProvider["send"]>>): void {
|
||||
this.wrappedProviderPromise.then((wrappedProvider: MetamaskProvider) => {
|
||||
wrappedProvider.send(...all);
|
||||
});
|
||||
}
|
||||
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `constructWrappedProvider`.
|
||||
getAddress(...all) {
|
||||
return this.getWrappedProviderOrThrow().getAddress(...all);
|
||||
}
|
||||
|
||||
// Returns the underlying wrapped provider.
|
||||
getWrappedProviderOrThrow() {
|
||||
getWrappedProviderOrThrow(): MetamaskProvider {
|
||||
if (this.wrappedProvider) {
|
||||
return this.wrappedProvider;
|
||||
} else {
|
||||
@@ -38,10 +40,12 @@ class MetaMaskTruffleProvider {
|
||||
}
|
||||
|
||||
// Returns a Promise that resolves to the wrapped provider.
|
||||
getOrConstructWrappedProvider() {
|
||||
getOrConstructWrappedProvider(): Promise<MetamaskProvider> {
|
||||
// Only if the network is MetaMask should we init the wrapped provider.
|
||||
if (argv.network != "metamask") {
|
||||
return;
|
||||
new Promise<MetamaskProvider>(() => {
|
||||
/* do nothing */
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
"Using MetaMask as your Truffle provider. To connect navigate your browser to http://localhost:3333 and sign into your account.\nAll transactions will be proceeded by your MetaMask wallet. Ensure that you do not switch your network during usage of the CLI utility."
|
||||
@@ -62,5 +66,3 @@ class MetaMaskTruffleProvider {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { MetaMaskTruffleProvider };
|
||||
+1
-3
@@ -1,7 +1,7 @@
|
||||
// Run the tests against 2 different kinds of token/synth decimal combinations:
|
||||
// 1) matching 18 collateral & 18 synthetic decimals with 18 decimals for price feed.
|
||||
// 3) matching 8 collateral & 8 synthetic decimals with 18 decimals for price feed.
|
||||
const TEST_DECIMAL_COMBOS = [
|
||||
export const TEST_DECIMAL_COMBOS = [
|
||||
{
|
||||
tokenSymbol: "WETH",
|
||||
tokenName: "Wrapped Ether",
|
||||
@@ -17,5 +17,3 @@ const TEST_DECIMAL_COMBOS = [
|
||||
priceFeedDecimals: 18,
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = { TEST_DECIMAL_COMBOS };
|
||||
+22
-13
@@ -1,23 +1,28 @@
|
||||
const web3 = require("web3");
|
||||
const lodash = require("lodash");
|
||||
const assert = require("assert");
|
||||
import Web3 from "web3";
|
||||
import lodash from "lodash";
|
||||
import assert from "assert";
|
||||
import { ZERO_ADDRESS } from "./Constants";
|
||||
|
||||
const { toWei, utf8ToHex, padRight } = web3.utils;
|
||||
const { ZERO_ADDRESS } = require("./Constants");
|
||||
const { toWei, utf8ToHex, padRight } = Web3.utils;
|
||||
|
||||
// Versions that production bots support.
|
||||
const SUPPORTED_CONTRACT_VERSIONS = [
|
||||
export const SUPPORTED_CONTRACT_VERSIONS = [
|
||||
{ contractType: "ExpiringMultiParty", contractVersion: "2.0.1" },
|
||||
{ contractType: "Perpetual", contractVersion: "2.0.1" },
|
||||
];
|
||||
|
||||
// Versions that unit tests will test against. Note we dont test anything less than 2.0.1 as all older contracts have
|
||||
// expired on mainnet.
|
||||
const TESTED_CONTRACT_VERSIONS = [
|
||||
export const TESTED_CONTRACT_VERSIONS = [
|
||||
{ contractType: "ExpiringMultiParty", contractVersion: "2.0.1" },
|
||||
{ contractType: "Perpetual", contractVersion: "2.0.1" },
|
||||
];
|
||||
|
||||
interface Version {
|
||||
contractType: string;
|
||||
contractVersion: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in conjunction with versionedIt within tests, this method will return true if the currentTestIterationVersion
|
||||
* is part of the SUPPORTED_CONTRACT_VERSIONS and supportedVersions (or is any), else returns false.
|
||||
@@ -27,7 +32,11 @@ const TESTED_CONTRACT_VERSIONS = [
|
||||
* @returns {bool} true of the current test iteration version is part of & supportedVersions & SUPPORTED_CONTRACT_VERSIONS
|
||||
* or any, false otherwise.
|
||||
*/
|
||||
function runTestForVersion(supportedVersions, SUPPORTED_CONTRACT_VERSIONS, currentTestIterationVersion) {
|
||||
export function runTestForVersion(
|
||||
supportedVersions: Version[],
|
||||
SUPPORTED_CONTRACT_VERSIONS: Version[],
|
||||
currentTestIterationVersion: Version
|
||||
): boolean {
|
||||
// Validate that the array of supportedVersions provided is in the SUPPORTED_CONTRACT_VERSIONS OR is `any`.
|
||||
const supportedVersionOverlap = lodash.intersectionBy(
|
||||
supportedVersions,
|
||||
@@ -59,11 +68,11 @@ function runTestForVersion(supportedVersions, SUPPORTED_CONTRACT_VERSIONS, curre
|
||||
* @param {Object} overrideConstructorParams optional override for the constructor params generated.
|
||||
* @returns {Object} version compatible constructor parameters.
|
||||
*/
|
||||
async function createConstructorParamsForContractVersion(
|
||||
contractVersion,
|
||||
contextObjects,
|
||||
export async function createConstructorParamsForContractVersion(
|
||||
contractVersion: Version,
|
||||
contextObjects: any,
|
||||
overrideConstructorParams = {}
|
||||
) {
|
||||
): Promise<any> {
|
||||
assert(
|
||||
contractVersion && contractVersion.contractVersion && contractVersion.contractType,
|
||||
"contractVersion must be provided, containing both a contract version and type"
|
||||
@@ -93,7 +102,7 @@ async function createConstructorParamsForContractVersion(
|
||||
(await contextObjects.timer.getCurrentTime?.())?.toNumber() ||
|
||||
parseInt(await contextObjects.timer.methods.getCurrentTime().call());
|
||||
|
||||
let constructorParams = {
|
||||
const constructorParams: any = {
|
||||
expirationTimestamp: currentTime + 100000,
|
||||
withdrawalLiveness: "1000",
|
||||
collateralAddress: contextObjects.collateralToken.address || contextObjects.collateralToken.options.address,
|
||||
@@ -1,5 +1,8 @@
|
||||
// Contains helpful methods for interacting with the Object data type.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ObjectType = { [key: string]: any };
|
||||
|
||||
/**
|
||||
* @notice Given `overrideProps` and `defaultProps` Objects, returns a new Object, `newObject`,
|
||||
* with the same properties as `defaultProps`, but replaces any property values that overlap with
|
||||
@@ -13,7 +16,7 @@
|
||||
* the new Object and `isValid` will be called to validate each of `newObject`'s properties.
|
||||
* @return `newObject` a new Object with the same properties as `defaultProps`, or `defaultProps` if undefined `overrideProps`.
|
||||
*/
|
||||
const createObjectFromDefaultProps = (overrideProps, defaultProps) => {
|
||||
export const createObjectFromDefaultProps = (overrideProps: ObjectType, defaultProps: ObjectType): ObjectType => {
|
||||
if (!defaultProps) {
|
||||
throw new Error("Undefined `defaultProps`");
|
||||
}
|
||||
@@ -22,7 +25,7 @@ const createObjectFromDefaultProps = (overrideProps, defaultProps) => {
|
||||
overrideProps = {};
|
||||
}
|
||||
|
||||
const newObject = {};
|
||||
const newObject: ObjectType = {};
|
||||
|
||||
Object.keys(defaultProps).forEach((prop) => {
|
||||
// Set property value to that contained in `overrideProps` if it exists, else set to `defaultProps`.
|
||||
+12
-14
@@ -1,9 +1,9 @@
|
||||
// Blacklisted price identifiers that will not automatically display on voter clients.
|
||||
const IDENTIFIER_BLACKLIST = { SOME_IDENTIFIER: ["1596666977"] };
|
||||
export const IDENTIFIER_BLACKLIST = { SOME_IDENTIFIER: ["1596666977"] };
|
||||
|
||||
// Price identifiers that should resolve prices to non 18 decimal precision. Any identifiers
|
||||
// not on this list are assumed to resolve to 18 decimals.
|
||||
const IDENTIFIER_NON_18_PRECISION = {
|
||||
export const IDENTIFIER_NON_18_PRECISION = {
|
||||
USDBTC: 8,
|
||||
"STABLESPREAD/USDC": 6,
|
||||
"STABLESPREAD/BTC": 8,
|
||||
@@ -19,8 +19,14 @@ const IDENTIFIER_NON_18_PRECISION = {
|
||||
TEST6DECIMALSANCIL: 6,
|
||||
};
|
||||
|
||||
const getPrecisionForIdentifier = (identifier) => {
|
||||
return IDENTIFIER_NON_18_PRECISION[identifier] ? IDENTIFIER_NON_18_PRECISION[identifier] : 18;
|
||||
type IdentifierNon18Precision = keyof typeof IDENTIFIER_NON_18_PRECISION;
|
||||
|
||||
function isNon18Precision(identifier: string): identifier is IdentifierNon18Precision {
|
||||
return identifier in IDENTIFIER_NON_18_PRECISION;
|
||||
}
|
||||
|
||||
export const getPrecisionForIdentifier = (identifier: string): number => {
|
||||
return isNon18Precision(identifier) ? IDENTIFIER_NON_18_PRECISION[identifier] : 18;
|
||||
};
|
||||
|
||||
// The optimistic oracle proposer should skip proposing prices for these identifiers, for expired EMP contracts,
|
||||
@@ -29,7 +35,7 @@ const getPrecisionForIdentifier = (identifier) => {
|
||||
// - "The type of price that the DVM will return is dependent on the timestamp the price request is made at. This
|
||||
// timestamp is the expiry timestamp of the contract that is intended to use this price identifier, so the TWAP
|
||||
// calculation is used pre-expiry and the closing index value of uSTONKS calculation is used at expiry.""
|
||||
const OPTIMISTIC_ORACLE_IGNORE_POST_EXPIRY = [
|
||||
export const OPTIMISTIC_ORACLE_IGNORE_POST_EXPIRY = [
|
||||
"TESTBLACKLIST", // Used for testing this list, assumed by tests to be at index 0.
|
||||
"uSTONKS_APR21",
|
||||
"GASETH-TWAP-1Mx1M",
|
||||
@@ -38,12 +44,4 @@ const OPTIMISTIC_ORACLE_IGNORE_POST_EXPIRY = [
|
||||
];
|
||||
|
||||
// Any identifier on this list
|
||||
const OPTIMISTIC_ORACLE_IGNORE = ["SPACEXLAUNCH", "uTVL_KPI_UMA"];
|
||||
|
||||
module.exports = {
|
||||
IDENTIFIER_BLACKLIST,
|
||||
IDENTIFIER_NON_18_PRECISION,
|
||||
getPrecisionForIdentifier,
|
||||
OPTIMISTIC_ORACLE_IGNORE_POST_EXPIRY,
|
||||
OPTIMISTIC_ORACLE_IGNORE,
|
||||
};
|
||||
export const OPTIMISTIC_ORACLE_IGNORE = ["SPACEXLAUNCH", "uTVL_KPI_UMA"];
|
||||
@@ -2,11 +2,13 @@
|
||||
// web3 providers to take advantage of the flexibility of providing custom configs to tailor the desired options. The network
|
||||
// syntax mimics that of the main UMA Truffle implementation to make this backwards compatible.
|
||||
|
||||
const Web3 = require("web3");
|
||||
const { getTruffleConfig, getNodeUrl } = require("./TruffleConfig");
|
||||
const argv = require("minimist")(process.argv.slice(), { string: ["network"] });
|
||||
const Url = require("url");
|
||||
const { RetryProvider } = require("./RetryProvider");
|
||||
import Web3 from "web3";
|
||||
import { getTruffleConfig, getNodeUrl, Network } from "./TruffleConfig";
|
||||
import minimist from "minimist";
|
||||
import Url from "url";
|
||||
import { RetryProvider, RetryConfig } from "./RetryProvider";
|
||||
import { AbstractProvider } from "web3-core";
|
||||
const argv = minimist(process.argv.slice(), { string: ["network"] });
|
||||
|
||||
// NODE_RETRY_CONFIG should be a JSON of the form (retries and delay are optional, they default to 1 and 0 respectively):
|
||||
// [
|
||||
@@ -24,34 +26,37 @@ const { RetryProvider } = require("./RetryProvider");
|
||||
const { NODE_RETRY_CONFIG } = process.env;
|
||||
|
||||
// Set web3 to null
|
||||
let web3 = null;
|
||||
let web3: Web3 | null = null;
|
||||
|
||||
function createBasicProvider(nodeRetryConfig) {
|
||||
export function createBasicProvider(nodeRetryConfig: RetryConfig[]): RetryProvider {
|
||||
return new RetryProvider(
|
||||
nodeRetryConfig.map((configElement) => {
|
||||
const protocol = Url.parse(configElement.url).protocol;
|
||||
let options = {
|
||||
timeout: 10000, // 10 second timeout
|
||||
};
|
||||
|
||||
if (protocol.startsWith("ws")) {
|
||||
// Websocket
|
||||
options = {
|
||||
...options,
|
||||
clientConfig: {
|
||||
maxReceivedFrameSize: 100000000, // Useful if requests result are large bytes - default: 1MiB
|
||||
maxReceivedMessageSize: 100000000, // bytes - default: 8MiB
|
||||
},
|
||||
reconnect: {
|
||||
auto: true, // Enable auto reconnection
|
||||
delay: 5000, // ms
|
||||
maxAttempts: 10,
|
||||
onTimeout: false,
|
||||
},
|
||||
nodeRetryConfig.map(
|
||||
(configElement: RetryConfig): RetryConfig => {
|
||||
const protocol = Url.parse(configElement.url).protocol;
|
||||
if (protocol === null) throw new Error(`No protocol detected for url: ${configElement.url}`);
|
||||
let options: RetryConfig["options"] = {
|
||||
timeout: 10000, // 10 second timeout
|
||||
};
|
||||
|
||||
if (protocol.startsWith("ws")) {
|
||||
// Websocket
|
||||
options = {
|
||||
...options,
|
||||
clientConfig: {
|
||||
maxReceivedFrameSize: 100000000, // Useful if requests result are large bytes - default: 1MiB
|
||||
maxReceivedMessageSize: 100000000, // bytes - default: 8MiB
|
||||
},
|
||||
reconnect: {
|
||||
auto: true, // Enable auto reconnection
|
||||
delay: 5000, // ms
|
||||
maxAttempts: 10,
|
||||
onTimeout: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ...configElement, options };
|
||||
}
|
||||
return { options, ...configElement };
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,11 +74,12 @@ function createBasicProvider(nodeRetryConfig) {
|
||||
* a `--network` argument. Useful in serverless or when running node scripts.
|
||||
*
|
||||
*/
|
||||
function getWeb3(parameterizedNetwork = "test") {
|
||||
export function getWeb3(parameterizedNetwork = "test"): Web3 {
|
||||
const castedGlobal = (global as unknown) as { web3: Web3 | undefined };
|
||||
if (castedGlobal.web3) return castedGlobal.web3;
|
||||
|
||||
// If a web3 instance has already been initialized, return it.
|
||||
if (web3) {
|
||||
return web3;
|
||||
}
|
||||
if (web3) return web3;
|
||||
|
||||
// Create basic web3 provider with no wallet connection based on the url alone.
|
||||
const network = argv.network || parameterizedNetwork; // Default to the test network (local network).
|
||||
@@ -84,7 +90,16 @@ function getWeb3(parameterizedNetwork = "test") {
|
||||
|
||||
// Use the basic provider to create a provider with an unlocked wallet. This piggybacks off the UMA common TruffleConfig
|
||||
// implementing all networks & wallet types. EG: mainnet_mnemonic, kovan_gckms. Errors if no argv.network.
|
||||
const providerWithWallet = getTruffleConfig().networks[network].provider(basicProvider);
|
||||
const provider = getTruffleConfig().networks[network].provider;
|
||||
|
||||
function isCallable(
|
||||
input: typeof provider
|
||||
): input is (inputProviderOrUrl?: AbstractProvider | string) => AbstractProvider {
|
||||
return input instanceof Function;
|
||||
}
|
||||
|
||||
if (!isCallable(provider)) throw new Error(`Null or string provider for network ${network}`);
|
||||
const providerWithWallet = provider(basicProvider);
|
||||
|
||||
// Lastly, create a web3 instance with the wallet-based provider. This can be used to query the chain via the
|
||||
// a basic web3 provider & has access to the users wallet based on the kind of connection they created.
|
||||
@@ -92,17 +107,3 @@ function getWeb3(parameterizedNetwork = "test") {
|
||||
|
||||
return web3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Gets a web3 instance based on the network argument using the truffle config in this package.
|
||||
* This method will be subbed in when called in a truffle test context.
|
||||
*/
|
||||
function getWeb3Test() {
|
||||
return global.web3;
|
||||
}
|
||||
|
||||
if (global.web3) {
|
||||
module.exports = { getWeb3: getWeb3Test };
|
||||
} else {
|
||||
module.exports = { getWeb3 };
|
||||
}
|
||||
@@ -3,11 +3,32 @@
|
||||
// ID as the network (i.e. Rinkeby will return 4 as the chainId), but some networks with chainID's > 255 need to
|
||||
// override the default behavior because their network ID is too high.
|
||||
const BRIDGE_CHAIN_ID = { 1337: 253, 80001: 254, 31337: 255 };
|
||||
const getBridgeChainId = (netId) => {
|
||||
return BRIDGE_CHAIN_ID[netId] || netId;
|
||||
|
||||
type ModifiedBridgeId = keyof typeof BRIDGE_CHAIN_ID;
|
||||
|
||||
function isModifedChainId(netId: number): netId is ModifiedBridgeId {
|
||||
return netId in BRIDGE_CHAIN_ID;
|
||||
}
|
||||
|
||||
export const getBridgeChainId = (netId: number): number => {
|
||||
return isModifedChainId(netId) ? BRIDGE_CHAIN_ID[netId] : netId;
|
||||
};
|
||||
|
||||
const PublicNetworks = {
|
||||
interface PublicNetworksType {
|
||||
[networkId: number]: {
|
||||
name: string;
|
||||
ethFaucet?: null | string;
|
||||
etherscan: string;
|
||||
daiAddress?: string;
|
||||
wethAddress?: string;
|
||||
customTruffleConfig?: {
|
||||
confirmations: number;
|
||||
timeoutBlocks: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const PublicNetworks: PublicNetworksType = {
|
||||
1: {
|
||||
name: "mainnet",
|
||||
ethFaucet: null,
|
||||
@@ -49,4 +70,6 @@ const PublicNetworks = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { PublicNetworks, getBridgeChainId };
|
||||
export function isPublicNetwork(name: string): boolean {
|
||||
return Object.values(PublicNetworks).some((network) => name.startsWith(network.name));
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const web3 = require("web3");
|
||||
|
||||
function getRandomSignedInt() {
|
||||
const unsignedValue = getRandomUnsignedInt();
|
||||
|
||||
// The signed range is just the unsigned range decreased by 2^255.
|
||||
const signedOffset = web3.utils.toBN(2).pow(web3.utils.toBN(255));
|
||||
return unsignedValue.sub(signedOffset);
|
||||
}
|
||||
|
||||
// Generate a random unsigned 256 bit int.
|
||||
function getRandomUnsignedInt() {
|
||||
return web3.utils.toBN(web3.utils.randomHex(32));
|
||||
}
|
||||
|
||||
module.exports = { getRandomSignedInt, getRandomUnsignedInt };
|
||||
@@ -0,0 +1,15 @@
|
||||
import Web3 from "web3";
|
||||
import { BN } from "./types";
|
||||
|
||||
export function getRandomSignedInt(): BN {
|
||||
const unsignedValue = getRandomUnsignedInt();
|
||||
|
||||
// The signed range is just the unsigned range decreased by 2^255.
|
||||
const signedOffset = Web3.utils.toBN(2).pow(Web3.utils.toBN(255));
|
||||
return unsignedValue.sub(signedOffset);
|
||||
}
|
||||
|
||||
// Generate a random unsigned 256 bit int.
|
||||
export function getRandomUnsignedInt(): BN {
|
||||
return Web3.utils.toBN(Web3.utils.randomHex(32));
|
||||
}
|
||||
@@ -1,8 +1,32 @@
|
||||
const assert = require("assert");
|
||||
const Web3 = require("web3");
|
||||
import Web3 from "web3";
|
||||
import assert from "assert";
|
||||
|
||||
type Web3ProviderOptions =
|
||||
| ConstructorParameters<typeof Web3.providers.HttpProvider>[1]
|
||||
| ConstructorParameters<typeof Web3.providers.WebsocketProvider>[1];
|
||||
|
||||
type Web3Provider =
|
||||
| InstanceType<typeof Web3.providers.HttpProvider>
|
||||
| InstanceType<typeof Web3.providers.WebsocketProvider>;
|
||||
|
||||
interface Config {
|
||||
retries: number;
|
||||
delay: number;
|
||||
url: string;
|
||||
options?: Web3ProviderOptions;
|
||||
}
|
||||
|
||||
type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>;
|
||||
type Payload = Parameters<Web3Provider["send"]>[0];
|
||||
type Callback = Parameters<Web3Provider["send"]>[1];
|
||||
type CallbackResult = Parameters<Callback>[1];
|
||||
|
||||
export type RetryConfig = PartialExcept<Config, "url">;
|
||||
|
||||
// Wraps one or more web3 http/websocket providers and allows per-request retries and fallbacks.
|
||||
class RetryProvider {
|
||||
export class RetryProvider {
|
||||
private providerCaches: (Config & { provider?: Web3Provider })[];
|
||||
|
||||
/**
|
||||
* @notice Constructs new retry provider.
|
||||
* @param {Array} config config object:
|
||||
@@ -22,21 +46,26 @@ class RetryProvider {
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
constructor(configs) {
|
||||
constructor(configs: RetryConfig[]) {
|
||||
assert(configs.length > 0, "Must have at least one provider");
|
||||
this.providerCaches = configs.map((config) => ({ retries: 1, delay: 0, ...config }));
|
||||
}
|
||||
|
||||
// Passes the send through, catches errors, and retries on error.
|
||||
send(payload, callback) {
|
||||
sendAsync(payload: Payload, callback: Callback): void {
|
||||
this.send(payload, callback);
|
||||
}
|
||||
|
||||
// Passes the send through, catches errors, and retries on error.
|
||||
send(payload: Payload, callback: Callback): void {
|
||||
// Turn callback into async-await internally.
|
||||
const sendWithProvider = (provider) => {
|
||||
const sendWithProvider = (provider: Web3Provider): Promise<CallbackResult> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
provider.send(payload, (error, result) => {
|
||||
if (error) {
|
||||
// Error thrown in the provider.
|
||||
reject(error);
|
||||
} else if (result.error) {
|
||||
} else if (result?.error) {
|
||||
// Error object returned from node.
|
||||
// TODO: we may need to add additional logic to discern EVM execution errors from node connection errors.
|
||||
reject(result);
|
||||
@@ -50,22 +79,22 @@ class RetryProvider {
|
||||
// Turn retry promise result back into a callback.
|
||||
this._runRetry(sendWithProvider).then(
|
||||
(result) => callback(null, result),
|
||||
(reason) => callback(reason, null)
|
||||
(reason) => callback(reason, undefined)
|
||||
);
|
||||
}
|
||||
|
||||
// Pass through disconnect to any initialized providers.
|
||||
disconnect(...all) {
|
||||
disconnect(code: number, reason: string): void {
|
||||
for (const cache of this.providerCaches) {
|
||||
cache?.provider.disconnect(...all);
|
||||
cache?.provider?.disconnect(code, reason);
|
||||
}
|
||||
}
|
||||
|
||||
supportsSubscriptions() {
|
||||
supportsSubscriptions(): boolean {
|
||||
return false; // return false for simplicity since some providers may be http, which doesn't support subscriptions.
|
||||
}
|
||||
|
||||
_constructOrGetProvider(index) {
|
||||
_constructOrGetProvider(index: number): Web3Provider {
|
||||
const cache = this.providerCaches[index];
|
||||
assert(cache, "No provider for this index");
|
||||
if (!cache.provider) {
|
||||
@@ -78,7 +107,7 @@ class RetryProvider {
|
||||
}
|
||||
|
||||
// Returns a Promise that resolves to the wrapped provider.
|
||||
async _runRetry(fn, providerIndex = 0, retryIndex = 0) {
|
||||
async _runRetry<T>(fn: (provider: Web3Provider) => Promise<T>, providerIndex = 0, retryIndex = 0): Promise<T> {
|
||||
const provider = this._constructOrGetProvider(providerIndex);
|
||||
try {
|
||||
return await fn(provider);
|
||||
@@ -86,8 +115,8 @@ class RetryProvider {
|
||||
const { delay, retries } = this.providerCaches[providerIndex];
|
||||
// If out of retries, move to next provider.
|
||||
const shouldMoveToNextProvider = retries <= retryIndex + 1;
|
||||
let nextRetryIndex = shouldMoveToNextProvider ? 0 : retryIndex + 1;
|
||||
let nextProviderIndex = shouldMoveToNextProvider ? providerIndex + 1 : providerIndex;
|
||||
const nextRetryIndex = shouldMoveToNextProvider ? 0 : retryIndex + 1;
|
||||
const nextProviderIndex = shouldMoveToNextProvider ? providerIndex + 1 : providerIndex;
|
||||
if (nextProviderIndex >= this.providerCaches.length) throw error; // No more providers to try.
|
||||
if (!shouldMoveToNextProvider) await new Promise((resolve) => setTimeout(resolve, delay * 1000)); // Delay only if not moving to a new provider.
|
||||
|
||||
@@ -96,5 +125,3 @@ class RetryProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { RetryProvider };
|
||||
@@ -1,4 +1,4 @@
|
||||
const SolcoverConfig = {
|
||||
export const SolcoverConfig = {
|
||||
providerOptions: { network_id: 1234 },
|
||||
skipFiles: [
|
||||
"Migrations.sol",
|
||||
@@ -8,5 +8,3 @@ const SolcoverConfig = {
|
||||
"oracle/implementation/test",
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = { SolcoverConfig };
|
||||
+52
-24
@@ -1,6 +1,8 @@
|
||||
import type Web3 from "web3";
|
||||
|
||||
// Attempts to execute a promise and returns false if no error is thrown,
|
||||
// or an Array of the error messages
|
||||
async function didContractThrow(promise) {
|
||||
export async function didContractThrow<T>(promise: Promise<T>): Promise<boolean> {
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
@@ -9,11 +11,22 @@ async function didContractThrow(promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function advanceBlockAndSetTime(web3, time) {
|
||||
type Web3Provider =
|
||||
| InstanceType<typeof Web3.providers.HttpProvider>
|
||||
| InstanceType<typeof Web3.providers.WebsocketProvider>;
|
||||
type Callback = Parameters<Web3Provider["send"]>[1];
|
||||
type CallbackResult = Parameters<Callback>[1];
|
||||
type CallbackError = Parameters<Callback>[0];
|
||||
|
||||
export async function advanceBlockAndSetTime(web3: Web3, time: number): Promise<CallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!web3.currentProvider || typeof web3?.currentProvider === "string" || !web3.currentProvider.send) {
|
||||
reject(new Error("No web3 provider that allows send()"));
|
||||
return;
|
||||
}
|
||||
web3.currentProvider.send(
|
||||
{ jsonrpc: "2.0", method: "evm_mine", params: [time], id: new Date().getTime() },
|
||||
(err, result) => {
|
||||
(err: CallbackError, result: CallbackResult) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -24,11 +37,16 @@ async function advanceBlockAndSetTime(web3, time) {
|
||||
});
|
||||
}
|
||||
|
||||
async function stopMining(web3) {
|
||||
export async function stopMining(web3: Web3): Promise<CallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!web3.currentProvider || typeof web3?.currentProvider === "string" || !web3.currentProvider.send) {
|
||||
reject(new Error("No web3 provider that allows send()"));
|
||||
return;
|
||||
}
|
||||
|
||||
web3.currentProvider.send(
|
||||
{ jsonrpc: "2.0", method: "miner_stop", params: [], id: new Date().getTime() },
|
||||
(err, result) => {
|
||||
(err: CallbackError, result: CallbackResult) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -39,11 +57,15 @@ async function stopMining(web3) {
|
||||
});
|
||||
}
|
||||
|
||||
async function startMining(web3) {
|
||||
async function startMining(web3: Web3): Promise<CallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!web3.currentProvider || typeof web3?.currentProvider === "string" || !web3.currentProvider.send) {
|
||||
reject(new Error("No web3 provider that allows send()"));
|
||||
return;
|
||||
}
|
||||
web3.currentProvider.send(
|
||||
{ jsonrpc: "2.0", method: "miner_start", params: [], id: new Date().getTime() },
|
||||
(err, result) => {
|
||||
(err: CallbackError, result: CallbackResult) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -54,11 +76,15 @@ async function startMining(web3) {
|
||||
});
|
||||
}
|
||||
|
||||
async function takeSnapshot(web3) {
|
||||
async function takeSnapshot(web3: Web3): Promise<CallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!web3.currentProvider || typeof web3?.currentProvider === "string" || !web3.currentProvider.send) {
|
||||
reject(new Error("No web3 provider that allows send()"));
|
||||
return;
|
||||
}
|
||||
web3.currentProvider.send(
|
||||
{ jsonrpc: "2.0", method: "evm_snapshot", id: new Date().getTime() },
|
||||
(err, snapshotId) => {
|
||||
{ jsonrpc: "2.0", method: "evm_snapshot", id: new Date().getTime(), params: [] },
|
||||
(err: CallbackError, snapshotId: CallbackResult) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
@@ -68,11 +94,15 @@ async function takeSnapshot(web3) {
|
||||
});
|
||||
}
|
||||
|
||||
async function revertToSnapshot(web3, id) {
|
||||
async function revertToSnapshot(web3: Web3, id: number): Promise<CallbackResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!web3.currentProvider || typeof web3?.currentProvider === "string" || !web3.currentProvider.send) {
|
||||
reject(new Error("No web3 provider that allows send()"));
|
||||
return;
|
||||
}
|
||||
web3.currentProvider.send(
|
||||
{ jsonrpc: "2.0", method: "evm_revert", params: [id], id: new Date().getTime() },
|
||||
(err, result) => {
|
||||
(err: CallbackError, result) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
@@ -82,13 +112,21 @@ async function revertToSnapshot(web3, id) {
|
||||
});
|
||||
}
|
||||
|
||||
type Contract = InstanceType<InstanceType<typeof Web3>["eth"]["Contract"]>;
|
||||
type Transaction = ReturnType<InstanceType<InstanceType<typeof Web3>["eth"]["Contract"]>["deploy"]>;
|
||||
|
||||
// This function will mine all transactions in `transactions` in the same block with block timestamp `time`.
|
||||
// The return value is an array of receipts corresponding to the transactions.
|
||||
// Each transaction in transactions should be a web3 transaction generated like:
|
||||
// let transaction = truffleContract.contract.methods.myMethodName(arg1, arg2);
|
||||
// or if already using a web3 contract object:
|
||||
// let transaction = web3Contract.methods.myMethodName(arg1, arg2);
|
||||
async function mineTransactionsAtTime(web3, transactions, time, sender) {
|
||||
export async function mineTransactionsAtTime(
|
||||
web3: Web3,
|
||||
transactions: Transaction[],
|
||||
time: number,
|
||||
sender: string
|
||||
): Promise<Contract[]> {
|
||||
await stopMining(web3);
|
||||
|
||||
try {
|
||||
@@ -97,7 +135,7 @@ async function mineTransactionsAtTime(web3, transactions, time, sender) {
|
||||
const result = transaction.send({ from: sender });
|
||||
|
||||
// Awaits the transactionHash, which signifies the transaction was sent, but not necessarily mined.
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on("transactionHash", function () {
|
||||
resolve();
|
||||
});
|
||||
@@ -120,13 +158,3 @@ async function mineTransactionsAtTime(web3, transactions, time, sender) {
|
||||
await startMining(web3);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
didContractThrow,
|
||||
mineTransactionsAtTime,
|
||||
advanceBlockAndSetTime,
|
||||
takeSnapshot,
|
||||
revertToSnapshot,
|
||||
stopMining,
|
||||
startMining,
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
const { UMA_FIRST_EMP_BLOCK } = require("./Constants.js");
|
||||
require("dotenv").config();
|
||||
import { UMA_FIRST_EMP_BLOCK } from "./Constants";
|
||||
import dotenv from "dotenv";
|
||||
import type Web3 from "web3";
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* @notice Return average block-time for a period.
|
||||
*/
|
||||
async function averageBlockTimeSeconds(/* lookbackSeconds */) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export async function averageBlockTimeSeconds(lookbackSeconds?: number): Promise<number> {
|
||||
// TODO: Call an external API to get this data. Currently this value is a hard-coded estimate
|
||||
// based on the data from https://etherscan.io/chart/blocktime. ~13.5 seconds has been the average
|
||||
// since April 2016, although this value seems to spike periodically for a relatively short period of time.
|
||||
@@ -18,7 +21,7 @@ async function averageBlockTimeSeconds(/* lookbackSeconds */) {
|
||||
}
|
||||
|
||||
// Sets fromBlock to the value of an environment variable if one is set. This can be set to 0 to make tests work with Ganache, or any other value needed for a production script or bot.
|
||||
async function getFromBlock(web3) {
|
||||
export async function getFromBlock(web3: Web3): Promise<number> {
|
||||
const networkType = await web3.eth.net.getNetworkType();
|
||||
if (process.env.FROM_BLOCK) {
|
||||
return Number(process.env.FROM_BLOCK);
|
||||
@@ -34,10 +37,8 @@ async function getFromBlock(web3) {
|
||||
* @param seconds the number of seconds.
|
||||
* @param cushionPercentage the percentage to add to the number as a cushion.
|
||||
*/
|
||||
async function estimateBlocksElapsed(seconds, cushionPercentage = 0.0) {
|
||||
export async function estimateBlocksElapsed(seconds: number, cushionPercentage = 0.0): Promise<number> {
|
||||
const cushionMultiplier = cushionPercentage + 1.0;
|
||||
const averageBlockTime = await averageBlockTimeSeconds();
|
||||
return Math.floor((seconds * cushionMultiplier) / averageBlockTime);
|
||||
}
|
||||
|
||||
module.exports = { averageBlockTimeSeconds, getFromBlock, estimateBlocksElapsed };
|
||||
@@ -1,6 +1,17 @@
|
||||
const util = require("util");
|
||||
const argv = require("minimist")(process.argv.slice(), {});
|
||||
const ynatm = require("@umaprotocol/ynatm");
|
||||
import util from "util";
|
||||
import minimist from "minimist";
|
||||
import ynatm from "@umaprotocol/ynatm";
|
||||
import type Web3 from "web3";
|
||||
import type { TransactionReceipt } from "web3-core";
|
||||
import type { ContractSendMethod, SendOptions } from "web3-eth-contract";
|
||||
|
||||
type CallReturnValue = ReturnType<ContractSendMethod["call"]>;
|
||||
interface AugmentedSendOptions extends SendOptions {
|
||||
chainId?: string;
|
||||
usingOffSetDSProxyAccount?: boolean;
|
||||
}
|
||||
|
||||
const argv = minimist(process.argv.slice(), {});
|
||||
|
||||
/**
|
||||
* Simulate transaction via .call() and then .send() and return receipt. If an error is thrown, return the error and add
|
||||
@@ -9,10 +20,24 @@ const ynatm = require("@umaprotocol/ynatm");
|
||||
* @notice Uses the ynatm package to retry the transaction with increasing gas price.
|
||||
* @param {*Object} web3.js object for making queries and accessing Ethereum related methods.
|
||||
* @param {*Object} transaction Transaction to call `.call()` and subsequently `.send()` on from `senderAccount`.
|
||||
* @param {*Object} config transaction config, e.g. { gasPrice, from }, passed to web3 transaction.
|
||||
* @param {*Object} transactionconfig transaction config, e.g. { gasPrice, from }, passed to web3 transaction.
|
||||
* @return Error and type of error (originating from `.call()` or `.send()`) or transaction receipt and return value.
|
||||
*/
|
||||
const runTransaction = async ({ web3, transaction, transactionConfig, availableAccounts = 1 }) => {
|
||||
export const runTransaction = async ({
|
||||
web3,
|
||||
transaction,
|
||||
transactionConfig,
|
||||
availableAccounts = 1,
|
||||
}: {
|
||||
web3: Web3;
|
||||
transaction: ContractSendMethod;
|
||||
transactionConfig: AugmentedSendOptions;
|
||||
availableAccounts: number;
|
||||
}): Promise<{
|
||||
receipt: TransactionReceipt;
|
||||
returnValue: CallReturnValue;
|
||||
transactionConfig: AugmentedSendOptions;
|
||||
}> => {
|
||||
// Add chainId in case RPC enforces transactions to be replay-protected, (i.e. enforced in geth v1.10,
|
||||
// https://blog.ethereum.org/2021/03/03/geth-v1-10-0/).
|
||||
transactionConfig.chainId = web3.utils.toHex(await web3.eth.getChainId());
|
||||
@@ -63,18 +88,21 @@ const runTransaction = async ({ web3, transaction, transactionConfig, availableA
|
||||
// hasn't mined. Min Gas price starts at caller's transactionConfig.gasPrice, with a max gasPrice of x6.
|
||||
const gasPriceScalingFunction = ynatm.DOUBLES;
|
||||
const retryDelay = 60000;
|
||||
if (!transactionConfig.gasPrice) throw new Error("No gas price provided");
|
||||
const minGasPrice = transactionConfig.gasPrice;
|
||||
const maxGasPrice = 2 * 3 * minGasPrice;
|
||||
const maxGasPrice = 2 * 3 * parseInt(minGasPrice);
|
||||
|
||||
const receipt = await ynatm.send({
|
||||
sendTransactionFunction: (gasPrice) => transaction.send({ ...transactionConfig, gasPrice }),
|
||||
sendTransactionFunction: (gasPrice: number) =>
|
||||
transaction.send({ ...transactionConfig, gasPrice: gasPrice.toString() }),
|
||||
minGasPrice,
|
||||
maxGasPrice,
|
||||
gasPriceScalingFunction,
|
||||
delay: retryDelay,
|
||||
});
|
||||
|
||||
return { receipt, returnValue, transactionConfig };
|
||||
// Note: cast is due to an incorrect type in the web3 declarations that assumes send returns a contract.
|
||||
return { receipt: (receipt as unknown) as TransactionReceipt, returnValue, transactionConfig };
|
||||
} catch (error) {
|
||||
error.type = "send";
|
||||
throw error;
|
||||
@@ -87,7 +115,7 @@ const runTransaction = async ({ web3, transaction, transactionConfig, availableA
|
||||
* @param {*string} account account to check.
|
||||
* @return Bool true if the account has pending transaction and false if no pending transaction.
|
||||
*/
|
||||
const accountHasPendingTransactions = async (web3, account) => {
|
||||
export const accountHasPendingTransactions = async (web3: Web3, account: string): Promise<boolean> => {
|
||||
const [currentMindedTransactions, currentTransactionsIncludingPending] = await Promise.all([
|
||||
web3.eth.getTransactionCount(account, "latest"),
|
||||
getPendingTransactionCount(web3, account),
|
||||
@@ -103,7 +131,9 @@ const accountHasPendingTransactions = async (web3, account) => {
|
||||
* @param {*string} account account to check.
|
||||
* @returns number representing the number of transactions, including pending.
|
||||
*/
|
||||
const getPendingTransactionCount = async (web3, account) => {
|
||||
export const getPendingTransactionCount = async (web3: Web3, account: string): Promise<number> => {
|
||||
if (!web3.currentProvider || typeof web3.currentProvider === "string" || !web3.currentProvider.send)
|
||||
throw new Error("A valid provider with send method not initialized");
|
||||
const sendRpc = util.promisify(web3.currentProvider.send).bind(web3.currentProvider);
|
||||
|
||||
const rpcResponse = await sendRpc({
|
||||
@@ -112,6 +142,7 @@ const getPendingTransactionCount = async (web3, account) => {
|
||||
params: [account, "pending"],
|
||||
id: Math.round(Math.random() * 100000),
|
||||
});
|
||||
if (!rpcResponse || !rpcResponse.result) throw new Error("Bad RPC response");
|
||||
return web3.utils.toDecimal(rpcResponse.result);
|
||||
};
|
||||
|
||||
@@ -121,7 +152,7 @@ const getPendingTransactionCount = async (web3, account) => {
|
||||
* @param {Object} web3 Provider from Truffle/node to connect to Ethereum network.
|
||||
* @param {number} blockerBlockNumber block execution until this block number is mined.
|
||||
*/
|
||||
const blockUntilBlockMined = async (web3, blockerBlockNumber, delay = 500) => {
|
||||
export const blockUntilBlockMined = async (web3: Web3, blockerBlockNumber: number, delay = 500): Promise<void> => {
|
||||
if (argv._.indexOf("test") !== -1) return;
|
||||
for (;;) {
|
||||
const currentBlockNumber = await web3.eth.getBlockNumber();
|
||||
@@ -129,5 +160,3 @@ const blockUntilBlockMined = async (web3, blockerBlockNumber, delay = 500) => {
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { runTransaction, blockUntilBlockMined, accountHasPendingTransactions };
|
||||
@@ -4,21 +4,33 @@
|
||||
* should never be shared publicly and ideally should not be stored in plain text.
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
import path from "path";
|
||||
import Web3 from "web3";
|
||||
import dotenv from "dotenv";
|
||||
import minimist from "minimist";
|
||||
import HDWalletProvider from "@truffle/hdwallet-provider";
|
||||
|
||||
const HDWalletProvider = require("@truffle/hdwallet-provider");
|
||||
const LedgerWalletProvider = require("@umaprotocol/truffle-ledger-provider");
|
||||
const { getGckmsConfig } = require("./gckms/GckmsConfig.js");
|
||||
const { ManagedSecretProvider } = require("./gckms/ManagedSecretProvider.js");
|
||||
const { PublicNetworks } = require("./PublicNetworks.js");
|
||||
const { MetaMaskTruffleProvider } = require("./MetaMaskTruffleProvider.js");
|
||||
const { isPublicNetwork } = require("./MigrationUtils");
|
||||
const Web3 = require("web3");
|
||||
require("dotenv").config();
|
||||
const argv = require("minimist")(process.argv.slice(), { string: ["gasPrice"] });
|
||||
import LedgerWalletProvider from "@umaprotocol/truffle-ledger-provider";
|
||||
import { getGckmsConfig } from "./gckms/GckmsConfig";
|
||||
import { ManagedSecretProvider } from "./gckms/ManagedSecretProvider";
|
||||
import { PublicNetworks, isPublicNetwork } from "./PublicNetworks";
|
||||
import { MetaMaskTruffleProvider } from "./MetaMaskTruffleProvider";
|
||||
|
||||
import type { AbstractProvider } from "web3-core";
|
||||
|
||||
dotenv.config();
|
||||
const argv = minimist(process.argv.slice(), { string: ["gasPrice"] });
|
||||
|
||||
export interface Network {
|
||||
networkCheckTimeout?: number;
|
||||
network_id?: number | string;
|
||||
gas?: number | string;
|
||||
gasPrice?: number | string;
|
||||
provider?: ((inputProviderOrUrl?: AbstractProvider | string) => AbstractProvider) | AbstractProvider | string;
|
||||
}
|
||||
|
||||
// Fallback to a public mnemonic to prevent exceptions.
|
||||
const mnemonic = process.env.MNEMONIC
|
||||
export const mnemonic = process.env.MNEMONIC
|
||||
? process.env.MNEMONIC
|
||||
: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat";
|
||||
|
||||
@@ -30,15 +42,15 @@ const privateKey = process.env.PRIVATE_KEY
|
||||
// Fallback to a backup non-prod API key.
|
||||
const keyOffset = process.env.KEY_OFFSET ? parseInt(process.env.KEY_OFFSET) : 0; // Start at account 0 by default.
|
||||
const numKeys = process.env.NUM_KEYS ? parseInt(process.env.NUM_KEYS) : 2; // Generate two wallets by default.
|
||||
let singletonProvider;
|
||||
let singletonProvider: AbstractProvider;
|
||||
|
||||
// Default options
|
||||
const gasPx = argv.gasPrice ? Web3.utils.toWei(argv.gasPrice, "gwei") : 1000000000; // 1 gwei
|
||||
const gasPx = argv.gasPrice ? Web3.utils.toWei(argv.gasPrice, "gwei").toString() : "1000000000"; // 1 gwei
|
||||
const gas = undefined; // Defining this as undefined (rather than leaving undefined) forces truffle estimate gas usage.
|
||||
const GckmsConfig = getGckmsConfig();
|
||||
|
||||
// If a custom node URL is provided, use that. Otherwise use an infura websocket connection.
|
||||
function getNodeUrl(networkName, useHttps = false) {
|
||||
export function getNodeUrl(networkName: string, useHttps = false): string {
|
||||
if (isPublicNetwork(networkName) && !networkName.includes("fork")) {
|
||||
const infuraApiKey = process.env.INFURA_API_KEY || "e34138b2db5b496ab5cc52319d2f0299";
|
||||
const name = networkName.split("_")[0];
|
||||
@@ -55,7 +67,12 @@ function getNodeUrl(networkName, useHttps = false) {
|
||||
// Adds a public network.
|
||||
// Note: All public networks can be accessed using keys from GCS using the ManagedSecretProvider or using a mnemonic in the
|
||||
// shell environment.
|
||||
function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
function addPublicNetwork(
|
||||
networks: { [name: string]: Network },
|
||||
name: string,
|
||||
networkId: number,
|
||||
customTruffleConfig: Network
|
||||
) {
|
||||
const options = {
|
||||
networkCheckTimeout: 15000,
|
||||
network_id: networkId,
|
||||
@@ -69,7 +86,7 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// GCS ManagedSecretProvider network.
|
||||
networks[name + "_gckms"] = {
|
||||
...options,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: AbstractProvider | string = nodeUrl) {
|
||||
if (!singletonProvider) {
|
||||
singletonProvider = new ManagedSecretProvider(GckmsConfig, provider, 0, GckmsConfig.length);
|
||||
}
|
||||
@@ -80,7 +97,7 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// Private key network.
|
||||
networks[name + "_privatekey"] = {
|
||||
...options,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: AbstractProvider | string = nodeUrl) {
|
||||
if (!singletonProvider) {
|
||||
singletonProvider = new HDWalletProvider([privateKey], provider);
|
||||
}
|
||||
@@ -91,7 +108,7 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// Mnemonic network.
|
||||
networks[name + "_mnemonic"] = {
|
||||
...options,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: AbstractProvider | string = nodeUrl) {
|
||||
if (!singletonProvider) {
|
||||
singletonProvider = new HDWalletProvider(mnemonic, provider, keyOffset, numKeys);
|
||||
}
|
||||
@@ -107,7 +124,7 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// Normal ledger wallet network.
|
||||
networks[name + "_ledger"] = {
|
||||
...options,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: AbstractProvider | string = nodeUrl) {
|
||||
if (!singletonProvider) {
|
||||
singletonProvider = new LedgerWalletProvider(ledgerOptions, provider);
|
||||
}
|
||||
@@ -119,7 +136,7 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// Note: the default derivation path matches the "legacy" ledger account in Ledger Live.
|
||||
networks[name + "_ledger_legacy"] = {
|
||||
...options,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: AbstractProvider | string = nodeUrl) {
|
||||
if (!singletonProvider) {
|
||||
singletonProvider = new LedgerWalletProvider(legacyLedgerOptions, provider);
|
||||
}
|
||||
@@ -131,33 +148,41 @@ function addPublicNetwork(networks, name, networkId, customTruffleConfig) {
|
||||
// Adds a local network.
|
||||
// Note: local networks generally have more varied parameters, so the user can override any network option by passing
|
||||
// a customOptions object.
|
||||
function addLocalNetwork(networks, name, customOptions) {
|
||||
function addLocalNetwork(networks: { [name: string]: Network }, name: string, customOptions?: Network) {
|
||||
const nodeUrl = getNodeUrl(name);
|
||||
const defaultOptions = {
|
||||
const defaultOptions: Network = {
|
||||
network_id: "*",
|
||||
gas: gas,
|
||||
gasPrice: gasPx,
|
||||
provider: function (provider = nodeUrl) {
|
||||
provider: function (provider: string | AbstractProvider = nodeUrl): AbstractProvider {
|
||||
// Don't use the singleton here because there's no reason to for local networks.
|
||||
|
||||
// Note: this is the way that truffle initializes their host + port http provider.
|
||||
// It is required to fix connection issues when testing.
|
||||
if (typeof provider === "string" && !provider.startsWith("ws")) {
|
||||
return new Web3.providers.HttpProvider(provider, { keepAlive: false });
|
||||
// Deprecated method abstract provider is required but unused. Force the type to be AbstractProvider.
|
||||
return (new Web3.providers.HttpProvider(provider, { keepAlive: false }) as unknown) as AbstractProvider;
|
||||
}
|
||||
const tempWeb3 = new Web3(provider);
|
||||
return tempWeb3.eth.currentProvider;
|
||||
if (
|
||||
!tempWeb3.eth.currentProvider ||
|
||||
typeof tempWeb3.eth.currentProvider === "string" ||
|
||||
!tempWeb3.eth.currentProvider.send
|
||||
)
|
||||
throw new Error("Web3 couldn't initialize provider");
|
||||
// Similar to the above, cast to abstract provider.
|
||||
return tempWeb3.eth.currentProvider as AbstractProvider;
|
||||
},
|
||||
};
|
||||
|
||||
networks[name] = { ...defaultOptions, ...customOptions };
|
||||
}
|
||||
|
||||
let networks = {};
|
||||
const networks = {};
|
||||
|
||||
// Public networks that need both a mnemonic and GCS ManagedSecretProvider network.
|
||||
for (const [id, { name, customTruffleConfig }] of Object.entries(PublicNetworks)) {
|
||||
addPublicNetwork(networks, name, id, customTruffleConfig);
|
||||
addPublicNetwork(networks, name, parseInt(id), customTruffleConfig);
|
||||
}
|
||||
|
||||
// Add test network.
|
||||
@@ -179,7 +204,30 @@ addLocalNetwork(networks, "metamask", {
|
||||
},
|
||||
});
|
||||
|
||||
function getTruffleConfig(truffleContextDir = "./") {
|
||||
interface TruffleConfig {
|
||||
networks: { [name: string]: Network };
|
||||
plugins: string[];
|
||||
mocha?: {
|
||||
enableTimeouts?: boolean;
|
||||
before_timeout?: number;
|
||||
};
|
||||
compilers: {
|
||||
solc: {
|
||||
version: string;
|
||||
settings?: {
|
||||
optimizer?: {
|
||||
enabled: boolean;
|
||||
runs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
migrations_directory?: string;
|
||||
contracts_directory?: string;
|
||||
contracts_build_directory?: string;
|
||||
}
|
||||
|
||||
export function getTruffleConfig(truffleContextDir = "./"): TruffleConfig {
|
||||
return {
|
||||
// See <http://truffleframework.com/docs/advanced/configuration>
|
||||
// for more about customizing your Truffle configuration!
|
||||
@@ -192,5 +240,3 @@ function getTruffleConfig(truffleContextDir = "./") {
|
||||
contracts_build_directory: path.join(truffleContextDir, "build/contracts"),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { getTruffleConfig, getNodeUrl, mnemonic };
|
||||
@@ -1,28 +1,32 @@
|
||||
// This file contains a number of useful uniswap v3 helpers helper functions.
|
||||
|
||||
const Web3 = require("web3");
|
||||
const { toBN, toWei } = Web3.utils;
|
||||
const { ethers } = require("ethers");
|
||||
import Web3 from "web3";
|
||||
import { BN } from "./types";
|
||||
import { ethers } from "ethers";
|
||||
import UniswapV3Pool from "@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json";
|
||||
import Decimal from "decimal.js";
|
||||
import bn from "bignumber.js"; // Big number that comes with web3 does not support square root.
|
||||
import { createContractObjectFromJson } from "./ContractUtils";
|
||||
|
||||
const UniswapV3Pool = require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json");
|
||||
const Decimal = require("decimal.js");
|
||||
const bn = require("bignumber.js"); // Big number that comes with web3 does not support square root.
|
||||
bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 });
|
||||
|
||||
const { createContractObjectFromJson } = require("./ContractUtils");
|
||||
const { toBN, toWei } = Web3.utils;
|
||||
|
||||
// Given a price defined in a ratio of reserve1 and reserve0 in x96, compute a price as sqrt(r1/r0) * 2^96
|
||||
function encodePriceSqrt(reserve1, reserve0) {
|
||||
export function encodePriceSqrt<T extends { toString: () => string }, U extends { toString: () => string }>(
|
||||
reserve1: T,
|
||||
reserve0: U
|
||||
): bn {
|
||||
return new bn(reserve1.toString()).div(reserve0.toString()).sqrt().multipliedBy(new bn(2).pow(96)).integerValue(3);
|
||||
}
|
||||
|
||||
// reverse x96 operation by compting (price/(2^96))^2
|
||||
function decodePriceSqrt(priseSqrt) {
|
||||
export function decodePriceSqrt(priseSqrt: string): bn {
|
||||
return new bn(priseSqrt).dividedBy(new bn(2).pow(96)).pow(2);
|
||||
}
|
||||
|
||||
// Fetch the decoded price from a uniswap pool from slot0.
|
||||
async function getCurrentPrice(poolAddress, web3) {
|
||||
export async function getCurrentPrice(poolAddress: string, web3: Web3): Promise<bn> {
|
||||
const pool = await createContractObjectFromJson(UniswapV3Pool, web3).at(poolAddress);
|
||||
|
||||
const slot0 = await pool.slot0();
|
||||
@@ -31,7 +35,7 @@ async function getCurrentPrice(poolAddress, web3) {
|
||||
}
|
||||
|
||||
// Encode a path. Note that pools (and therefore paths) change when you use different fees.
|
||||
function encodePath(path, fees) {
|
||||
export function encodePath(path: string[], fees: BN[] | string[] | number[]): string {
|
||||
const FEE_SIZE = 3;
|
||||
if (path.length != fees.length + 1) {
|
||||
throw new Error("path/fee lengths do not match");
|
||||
@@ -50,15 +54,17 @@ function encodePath(path, fees) {
|
||||
return encoded.toLowerCase();
|
||||
}
|
||||
|
||||
function toBNWei(input) {
|
||||
export function toBNWei(input: BN | string | number): BN {
|
||||
return toBN(toWei(input.toString()));
|
||||
}
|
||||
|
||||
export type FeeAmount = keyof typeof TICK_SPACINGS;
|
||||
|
||||
// The price in a pool, in terms of tick number, can be expressed as: p=1.0001^tick (taken from uniswapV3 white paper).
|
||||
// Solving for the tick in terms of the price yields tick≈1.00005*ln(p) (calculated on wolframalpha). When creating a
|
||||
// position, the tick needs to be a multiple of the TICK_SPACING for that particular fee. This method therefore computes
|
||||
// a valid tick for a given price and poolFee.
|
||||
function getTickFromPrice(price, poolFee) {
|
||||
export function getTickFromPrice(price: BN | string | number, poolFee: FeeAmount): string {
|
||||
return toBNWei(10000.5)
|
||||
.mul(toBNWei(new Decimal(price.toString()).ln().toFixed(10)))
|
||||
.div(toBNWei(1))
|
||||
@@ -68,14 +74,14 @@ function getTickFromPrice(price, poolFee) {
|
||||
.toString();
|
||||
}
|
||||
|
||||
function getMinTick(tickSpacing) {
|
||||
export function getMinTick(tickSpacing: number): number {
|
||||
return Math.ceil(-887272 / tickSpacing) * tickSpacing;
|
||||
}
|
||||
function getMaxTick(tickSpacing) {
|
||||
export function getMaxTick(tickSpacing: number): number {
|
||||
return Math.floor(887272 / tickSpacing) * tickSpacing;
|
||||
}
|
||||
|
||||
function getTickBitmapIndex(tick, tickSpacing) {
|
||||
export function getTickBitmapIndex(tick: number | BN | string, tickSpacing: number): BN {
|
||||
const intermediate = toBN(tick.toString()).div(toBN(tickSpacing.toString()));
|
||||
|
||||
// see https://docs.soliditylang.org/en/v0.7.6/types.html#shifts
|
||||
@@ -88,7 +94,7 @@ function getTickBitmapIndex(tick, tickSpacing) {
|
||||
else return intermediate.shrn(8);
|
||||
}
|
||||
|
||||
function computePoolAddress(factoryAddress, tokenA, tokenB, fee) {
|
||||
export function computePoolAddress(factoryAddress: string, tokenA: string, tokenB: string, fee: FeeAmount): string {
|
||||
const POOL_BYTECODE_HASH = ethers.utils.keccak256(UniswapV3Pool.bytecode);
|
||||
const [token0, token1] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB] : [tokenB, tokenA];
|
||||
const constructorArgumentsEncoded = ethers.utils.defaultAbiCoder.encode(
|
||||
@@ -107,20 +113,6 @@ function computePoolAddress(factoryAddress, tokenA, tokenB, fee) {
|
||||
return ethers.utils.getAddress(`0x${ethers.utils.keccak256(sanitizedInputs).slice(-40)}`);
|
||||
}
|
||||
|
||||
const FeeAmount = { LOW: 500, MEDIUM: 3000, HIGH: 10000 };
|
||||
export const FeeAmount = { LOW: 500, MEDIUM: 3000, HIGH: 10000 };
|
||||
|
||||
const TICK_SPACINGS = { [FeeAmount.LOW]: 10, [FeeAmount.MEDIUM]: 60, [FeeAmount.HIGH]: 200 };
|
||||
|
||||
module.exports = {
|
||||
getTickFromPrice,
|
||||
encodePriceSqrt,
|
||||
decodePriceSqrt,
|
||||
getCurrentPrice,
|
||||
encodePath,
|
||||
getMinTick,
|
||||
getMaxTick,
|
||||
getTickBitmapIndex,
|
||||
computePoolAddress,
|
||||
FeeAmount,
|
||||
TICK_SPACINGS,
|
||||
};
|
||||
export const TICK_SPACINGS = { [FeeAmount.LOW]: 10, [FeeAmount.MEDIUM]: 60, [FeeAmount.HIGH]: 200 };
|
||||
@@ -1,217 +0,0 @@
|
||||
const {
|
||||
decryptMessage,
|
||||
encryptMessage,
|
||||
deriveKeyPairFromSignatureTruffle,
|
||||
deriveKeyPairFromSignatureMetamask,
|
||||
} = require("./Crypto");
|
||||
const { getKeyGenMessage, computeVoteHash } = require("./EncryptionHelper");
|
||||
const { BATCH_MAX_COMMITS, BATCH_MAX_RETRIEVALS, BATCH_MAX_REVEALS } = require("./Constants");
|
||||
const { getRandomUnsignedInt } = require("./Random.js");
|
||||
const { parseFixed } = require("./FormattingUtils");
|
||||
|
||||
const argv = require("minimist")(process.argv.slice());
|
||||
|
||||
/**
|
||||
* Return voting contract, voting account, and signing account based on whether user is using a
|
||||
* designated voting proxy.
|
||||
* @param {String} account current default signing account
|
||||
* @param {Object} voting DVM contract
|
||||
* @param {Object} [designatedVoting] designated voting proxy contract
|
||||
* @return votingContract Contract to send votes to.
|
||||
* @return votingAccount address that votes are attributed to.
|
||||
* @return signingAddress address used to sign encrypted messages.
|
||||
*/
|
||||
const getVotingRoles = (account, voting, designatedVoting) => {
|
||||
if (designatedVoting) {
|
||||
return { votingContract: designatedVoting, votingAccount: designatedVoting.address, signingAddress: account };
|
||||
} else {
|
||||
return { votingContract: voting, votingAccount: account, signingAddress: account };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a salt and use it to encrypt a committed vote in response to a price request
|
||||
* Return committed vote details to the voter.
|
||||
* @param {Object} request {identifier, time}
|
||||
* @param {String} roundId
|
||||
* @param {Object} web3
|
||||
* @param {String | Number | BN} price
|
||||
* @param {String} signingAccount
|
||||
* @param {String} votingAccount
|
||||
* @param {String?} decimals Default 18 decimal precision for price
|
||||
*/
|
||||
const constructCommitment = async (request, roundId, web3, price, signingAccount, votingAccount, decimals = 18) => {
|
||||
const priceWei = parseFixed(price.toString(), decimals).toString();
|
||||
const salt = getRandomUnsignedInt().toString();
|
||||
const hash = computeVoteHash({
|
||||
price: priceWei,
|
||||
salt,
|
||||
account: votingAccount,
|
||||
time: request.time,
|
||||
roundId,
|
||||
identifier: request.identifier,
|
||||
});
|
||||
|
||||
const vote = { price: priceWei, salt };
|
||||
let publicKey;
|
||||
if (argv.network === "metamask") {
|
||||
publicKey = (await deriveKeyPairFromSignatureMetamask(web3, getKeyGenMessage(roundId), signingAccount)).publicKey;
|
||||
} else {
|
||||
publicKey = (await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), signingAccount)).publicKey;
|
||||
}
|
||||
const encryptedVote = await encryptMessage(publicKey, JSON.stringify(vote));
|
||||
|
||||
return { identifier: request.identifier, time: request.time, hash, encryptedVote, price: priceWei, salt };
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt an encrypted vote commit for the voter and return vote details
|
||||
* @param {Object} request {identifier, time}
|
||||
* @param {String} roundId
|
||||
* @param {Object} web3
|
||||
* @param {String} signingAccount
|
||||
* @param {Object} votingContract deployed Voting.sol instance
|
||||
* @param {String} votingAccount
|
||||
*/
|
||||
const constructReveal = async (request, roundId, web3, signingAccount, votingContract, votingAccount) => {
|
||||
const encryptedCommit = (await getLatestEvent("EncryptedVote", request, roundId, votingAccount, votingContract))
|
||||
.encryptedVote;
|
||||
|
||||
let privateKey;
|
||||
if (argv.network === "metamask") {
|
||||
privateKey = (await deriveKeyPairFromSignatureMetamask(web3, getKeyGenMessage(roundId), signingAccount)).privateKey;
|
||||
} else {
|
||||
privateKey = (await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), signingAccount)).privateKey;
|
||||
}
|
||||
const vote = JSON.parse(await decryptMessage(privateKey, encryptedCommit));
|
||||
|
||||
return { identifier: request.identifier, time: request.time, price: vote.price.toString(), salt: vote.salt };
|
||||
};
|
||||
|
||||
// Optimally batch together vote commits in the fewest batches possible,
|
||||
// each batchCommit is one Ethereum transaction. Return the number of successes
|
||||
// and batches to the user
|
||||
const batchCommitVotes = async (newCommitments, votingContract, account) => {
|
||||
let successes = [];
|
||||
let batches = 0;
|
||||
for (let k = 0; k < newCommitments.length; k += BATCH_MAX_COMMITS) {
|
||||
const maxBatchSize = newCommitments.slice(k, Math.min(k + BATCH_MAX_COMMITS, newCommitments.length));
|
||||
|
||||
// Always call `batchCommit`, even if there's only one commitment. Difference in gas cost is negligible.
|
||||
const { receipt } = await votingContract.batchCommit(
|
||||
maxBatchSize.map((commitment) => {
|
||||
// This filters out the parts of the commitment that we don't need to send to solidity.
|
||||
// Note: this isn't strictly necessary since web3 will only encode variables that share names with properties in
|
||||
// the solidity struct.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { price, salt, ...rest } = commitment;
|
||||
return rest;
|
||||
}),
|
||||
{ from: account }
|
||||
);
|
||||
|
||||
// Add the batch transaction hash to each commitment.
|
||||
maxBatchSize.forEach((commitment) => {
|
||||
commitment.txnHash = receipt.transactionHash;
|
||||
});
|
||||
|
||||
// Append any of new batch's commitments to running commitment list
|
||||
successes = successes.concat(maxBatchSize);
|
||||
batches += 1;
|
||||
}
|
||||
|
||||
return { successes, batches };
|
||||
};
|
||||
|
||||
// Optimally batch together vote reveals in the fewest batches possible,
|
||||
// each batchReveal is one Ethereum transaction. Return the number of successes
|
||||
// and batches to the user
|
||||
const batchRevealVotes = async (newReveals, votingContract, account) => {
|
||||
let successes = [];
|
||||
let batches = 0;
|
||||
for (let k = 0; k < newReveals.length; k += BATCH_MAX_REVEALS) {
|
||||
const maxBatchSize = newReveals.slice(k, Math.min(k + BATCH_MAX_REVEALS, newReveals.length));
|
||||
|
||||
// Always call `batchReveal`, even if there's only one reveal. Difference in gas cost is negligible.
|
||||
const { receipt } = await votingContract.batchReveal(maxBatchSize, { from: account });
|
||||
|
||||
// Add the batch transaction hash to each reveal.
|
||||
maxBatchSize.forEach((reveal) => {
|
||||
reveal.txnHash = receipt.transactionHash;
|
||||
});
|
||||
|
||||
// Append any of new batch's commitments to running commitment list
|
||||
successes = successes.concat(maxBatchSize);
|
||||
batches += 1;
|
||||
}
|
||||
|
||||
return { successes, batches };
|
||||
};
|
||||
|
||||
// Optimally batch together reward retrievals in the fewest batches possible,
|
||||
// each retrieveRewards is one Ethereum transaction. Return the number of successes
|
||||
// and batches to the user
|
||||
const batchRetrieveRewards = async (requests, roundId, votingContract, votingAccount, signingAccount) => {
|
||||
let successes = [];
|
||||
let batches = 0;
|
||||
for (let i = 0; i < requests.length; i += BATCH_MAX_RETRIEVALS) {
|
||||
const maxBatchSize = requests.slice(i, Math.min(i + BATCH_MAX_RETRIEVALS, requests.length));
|
||||
const pendingRequests = [];
|
||||
for (let j = 0; j < maxBatchSize.length; j++) {
|
||||
pendingRequests.push({ identifier: maxBatchSize[j].identifier, time: maxBatchSize[j].time });
|
||||
}
|
||||
|
||||
// Always call `retrieveRewards`, even if there's only one reward. Difference in gas cost is negligible.
|
||||
const { receipt } = await votingContract.retrieveRewards(votingAccount, roundId, pendingRequests, {
|
||||
from: signingAccount,
|
||||
});
|
||||
|
||||
// Add the batch transaction hash to each reveal.
|
||||
maxBatchSize.forEach((retrieve) => {
|
||||
retrieve.txnHash = receipt.transactionHash;
|
||||
});
|
||||
|
||||
// Append any of new batch's commitments to running commitment list
|
||||
successes = successes.concat(maxBatchSize);
|
||||
batches += 1;
|
||||
}
|
||||
|
||||
return { successes, batches };
|
||||
};
|
||||
|
||||
// Get the latest event matching the provided parameters. Assumes that all events from Voting.sol have indexed
|
||||
// parameters for identifier, roundId, and voter.
|
||||
const getLatestEvent = async (eventName, request, roundId, account, votingContract) => {
|
||||
const events = await votingContract.getPastEvents(eventName, {
|
||||
fromBlock: 0,
|
||||
filter: { identifier: request.identifier, roundId: roundId.toString(), voter: account.toString() },
|
||||
});
|
||||
// Sort descending. Primary sort on block number. Secondary sort on transactionIndex. Tertiary sort on logIndex.
|
||||
events.sort((a, b) => {
|
||||
if (a.blockNumber !== b.blockNumber) {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
}
|
||||
|
||||
if (a.transactionIndex !== b.transactionIndex) {
|
||||
return b.transactionIndex - a.transactionIndex;
|
||||
}
|
||||
|
||||
return b.logIndex - a.logIndex;
|
||||
});
|
||||
for (const ev of events) {
|
||||
if (ev.returnValues.time.toString() === request.time.toString()) {
|
||||
return ev.returnValues;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getVotingRoles,
|
||||
getLatestEvent,
|
||||
constructCommitment,
|
||||
constructReveal,
|
||||
batchCommitVotes,
|
||||
batchRevealVotes,
|
||||
batchRetrieveRewards,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// Only the browser-safe modules.
|
||||
export * from "./AbiUtils";
|
||||
export * from "./AdminUtils";
|
||||
export * from "./Constants";
|
||||
export * from "./Constants";
|
||||
export * from "./AbiUtils";
|
||||
export * from "./AdminUtils";
|
||||
export * from "./ContractUtils";
|
||||
export * from "./TransactionUtils";
|
||||
export * from "./Crypto";
|
||||
export * from "./EmpUtils";
|
||||
export * from "./EncryptionHelper";
|
||||
export * from "./Enums";
|
||||
export * from "./FormattingUtils";
|
||||
export * from "./ObjectUtils";
|
||||
export * from "./PublicNetworks";
|
||||
export * from "./Random";
|
||||
export * from "./SolcoverConfig";
|
||||
export * from "./SolidityTestUtils";
|
||||
export * from "./TimeUtils";
|
||||
export * from "./PriceIdentifierUtils";
|
||||
export * from "./MultiDecimalTestHelper";
|
||||
export * from "./MultiVersionTestHelpers";
|
||||
export * from "./hardhat/fixtures";
|
||||
@@ -1,112 +0,0 @@
|
||||
// Example usage:
|
||||
// $(npm bin)/truffle exec <some_script> --network test --keys priceFeed --keys registry
|
||||
|
||||
const argv = require("minimist")(process.argv.slice());
|
||||
const fs = require("fs");
|
||||
require("dotenv").config();
|
||||
|
||||
// Grab the name property from each to get a list of the names of the public networks.
|
||||
const publicNetworkNames = Object.values(require("../PublicNetworks.js").PublicNetworks).map((elt) => elt.name);
|
||||
const { isPublicNetwork } = require("../MigrationUtils.js");
|
||||
|
||||
function arrayify(input) {
|
||||
if (!input) return [];
|
||||
if (!Array.isArray(input)) return [input];
|
||||
return input;
|
||||
}
|
||||
|
||||
// Note: this default config should not be used - it is intended to communicate the structure of the config.
|
||||
// .gcloudKmsOverride.js should export your real config.
|
||||
function getDefaultStaticConfig() {
|
||||
// The anatomy of an individual config is:
|
||||
// projectId: ID of a Google Cloud project
|
||||
// keyRingId: ID of keyring
|
||||
// cryptoKeyId: ID of the crypto key to use for decrypting the key material
|
||||
// locationId: Google Cloud location, e.g., 'global'.
|
||||
// ciphertextBucket: ID of a Google Cloud storage bucket.
|
||||
// ciphertextFilename: Name of a file within `ciphertextBucket`.
|
||||
|
||||
const defaultConfig = {
|
||||
private: {
|
||||
deployer: {},
|
||||
registry: {},
|
||||
store: {},
|
||||
priceFeed: {},
|
||||
sponsorWhitelist: {},
|
||||
returnCalculatorWhitelist: {},
|
||||
marginCurrencyWhitelist: {},
|
||||
// This is an example to show you what a typical config for a gcloud-stored config might look like.
|
||||
example: {
|
||||
projectId: "project-name",
|
||||
locationId: "asia-east2",
|
||||
keyRingId: "Keyring_Test",
|
||||
cryptoKeyId: "keyname",
|
||||
ciphertextBucket: "cipher_bucket",
|
||||
ciphertextFilename: "ciphertext_fname.enc",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Add a blank network config for all public networks so they don't fail to process but will fail if selected.
|
||||
const blankNetworkConfig = {
|
||||
deployer: {},
|
||||
registry: {},
|
||||
store: {},
|
||||
priceFeed: {},
|
||||
sponsorWhitelist: {},
|
||||
returnCalculatorWhitelist: {},
|
||||
marginCurrencyWhitelist: {},
|
||||
};
|
||||
|
||||
for (let name of publicNetworkNames) {
|
||||
defaultConfig[name] = blankNetworkConfig;
|
||||
}
|
||||
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
function getGckmsConfig(keys = arrayify(argv.keys), network = argv.network) {
|
||||
let configOverride = {};
|
||||
|
||||
// If there is no env variable providing the config, attempt to pull it from a file.
|
||||
// TODO: this is kinda hacky. We should refactor this to only take in the config using one method.
|
||||
if (process.env.GCKMS_CONFIG) {
|
||||
// If the env variable is present, just take that json.
|
||||
configOverride = JSON.parse(process.env.GCKMS_CONFIG);
|
||||
} else {
|
||||
// Import the .GckmsOverride.js file if it exists.
|
||||
// Note: this file is expected to be present in the same directory as this script.
|
||||
let overrideFname = ".GckmsOverride.js";
|
||||
try {
|
||||
if (fs.existsSync(`${__dirname}/${overrideFname}`)) {
|
||||
configOverride = require(`./${overrideFname}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const getNetworkName = () => {
|
||||
if (isPublicNetwork(network || "")) {
|
||||
// Take everything before the underscore:
|
||||
// mainnet_gckms -> mainnet.
|
||||
return network.split("_")[0];
|
||||
}
|
||||
|
||||
return "mainnet";
|
||||
};
|
||||
|
||||
// Compose the exact config for this network.
|
||||
const staticConfig = { ...getDefaultStaticConfig(), ...configOverride };
|
||||
const networkConfig = staticConfig[getNetworkName()];
|
||||
|
||||
// Provide the configs for the keys requested.
|
||||
const keyConfigs = keys.map((keyName) => {
|
||||
return networkConfig[keyName];
|
||||
});
|
||||
|
||||
return keyConfigs;
|
||||
}
|
||||
|
||||
// Export the requested config.
|
||||
module.exports = { getGckmsConfig };
|
||||
@@ -0,0 +1,78 @@
|
||||
// Example usage:
|
||||
// $(npm bin)/truffle exec <some_script> --network test --keys priceFeed --keys registry
|
||||
import minimist from "minimist";
|
||||
import fs from "fs";
|
||||
import dotenv from "dotenv";
|
||||
import { isPublicNetwork } from "../PublicNetworks";
|
||||
|
||||
const argv = minimist(process.argv.slice());
|
||||
dotenv.config();
|
||||
|
||||
// The anatomy of an individual config is:
|
||||
// projectId: ID of a Google Cloud project
|
||||
// keyRingId: ID of keyring
|
||||
// cryptoKeyId: ID of the crypto key to use for decrypting the key material
|
||||
// locationId: Google Cloud location, e.g., 'global'.
|
||||
// ciphertextBucket: ID of a Google Cloud storage bucket.
|
||||
// ciphertextFilename: Name of a file within `ciphertextBucket`.
|
||||
export interface KeyConfig {
|
||||
projectId: string;
|
||||
locationId: string;
|
||||
keyRingId: string;
|
||||
cryptoKeyId: string;
|
||||
ciphertextBucket: string;
|
||||
ciphertextFilename: string;
|
||||
}
|
||||
export interface GckmsConfig {
|
||||
[network: string]: {
|
||||
[keyName: string]: KeyConfig;
|
||||
};
|
||||
}
|
||||
|
||||
function arrayify(input: string[] | string | undefined): string[] {
|
||||
if (!input) return [];
|
||||
if (!Array.isArray(input)) return [input];
|
||||
return input;
|
||||
}
|
||||
|
||||
export function getGckmsConfig(keys = arrayify(argv.keys), network = argv.network): KeyConfig[] {
|
||||
let configOverride: GckmsConfig = {};
|
||||
|
||||
// If there is no env variable providing the config, attempt to pull it from a file.
|
||||
// TODO: this is kinda hacky. We should refactor this to only take in the config using one method.
|
||||
if (process.env.GCKMS_CONFIG) {
|
||||
// If the env variable is present, just take that json.
|
||||
configOverride = JSON.parse(process.env.GCKMS_CONFIG);
|
||||
} else {
|
||||
// Import the .GckmsOverride.js file if it exists.
|
||||
// Note: this file is expected to be present in the same directory as this script.
|
||||
const overrideFname = ".GckmsOverride.js";
|
||||
try {
|
||||
if (fs.existsSync(`${__dirname}/${overrideFname}`)) {
|
||||
configOverride = JSON.parse(fs.readFileSync(`./${overrideFname}`).toString("utf8"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const getNetworkName = () => {
|
||||
if (isPublicNetwork(network || "unknown")) {
|
||||
// Take everything before the underscore:
|
||||
// mainnet_gckms -> mainnet.
|
||||
return network.split("_")[0];
|
||||
}
|
||||
|
||||
return "mainnet";
|
||||
};
|
||||
|
||||
// Compose the exact config for this network.
|
||||
const networkConfig = configOverride[getNetworkName()];
|
||||
|
||||
// Provide the configs for the keys requested.
|
||||
const keyConfigs = keys.map((keyName) => {
|
||||
return networkConfig[keyName] || {};
|
||||
});
|
||||
|
||||
return keyConfigs;
|
||||
}
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
const { retrieveGckmsKeys } = require("./utils");
|
||||
const { extendConfig } = require("hardhat/config");
|
||||
const { HardhatPluginError } = require("hardhat/plugins");
|
||||
const { getGckmsConfig } = require("./GckmsConfig");
|
||||
import { retrieveGckmsKeys } from "./utils";
|
||||
import { extendConfig } from "hardhat/config";
|
||||
import { HardhatPluginError } from "hardhat/plugins";
|
||||
import { getGckmsConfig } from "./GckmsConfig";
|
||||
|
||||
// This plugin just injects GCKMS keys into the config.
|
||||
// Because it does so asynchonously, it creates a race condition. This means it may not work in all circumstances.
|
||||
+25
-15
@@ -1,9 +1,14 @@
|
||||
const HDWalletProvider = require("@truffle/hdwallet-provider");
|
||||
const { retrieveGckmsKeys } = require("./utils");
|
||||
import HDWalletProvider from "@truffle/hdwallet-provider";
|
||||
import { retrieveGckmsKeys } from "./utils";
|
||||
import type { KeyConfig } from "./GckmsConfig";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Tail2<T extends any[]> = T extends [unknown, unknown, ...infer R] ? R : never;
|
||||
type RemainingHDWalletArgs = Tail2<ConstructorParameters<typeof HDWalletProvider>>;
|
||||
|
||||
// Wraps HDWalletProvider, deferring construction and allowing a Cloud KMS managed secret to be fetched asynchronously
|
||||
// and used to initialize an HDWalletProvider.
|
||||
class ManagedSecretProvider {
|
||||
export class ManagedSecretProvider {
|
||||
// cloudKmsSecretConfigs must either be:
|
||||
// a single config object which representing a mnemonic or a private key on GCKMS
|
||||
// or
|
||||
@@ -15,37 +20,44 @@ class ManagedSecretProvider {
|
||||
// locationId: Google Cloud location, e.g., 'global'.
|
||||
// ciphertextBucket: ID of a Google Cloud storage bucket.
|
||||
// ciphertextFilename: Name of a file within `ciphertextBucket`.
|
||||
constructor(cloudKmsSecretConfigs, ...remainingArgs) {
|
||||
private readonly remainingArgs: RemainingHDWalletArgs;
|
||||
private wrappedProvider: null | HDWalletProvider;
|
||||
private wrappedProviderPromise: Promise<HDWalletProvider>;
|
||||
constructor(
|
||||
private readonly cloudKmsSecretConfigs: KeyConfig[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private readonly providerOrUrl: string | any, // Mirrors the type that HDWalletProvider expects.
|
||||
...remainingArgs: RemainingHDWalletArgs
|
||||
) {
|
||||
if (!Array.isArray(cloudKmsSecretConfigs)) {
|
||||
cloudKmsSecretConfigs = [cloudKmsSecretConfigs];
|
||||
}
|
||||
this.cloudKmsSecretConfigs = cloudKmsSecretConfigs;
|
||||
this.remainingArgs = remainingArgs;
|
||||
this.wrappedProvider = null;
|
||||
this.wrappedProviderPromise = this.getOrConstructWrappedProvider();
|
||||
}
|
||||
|
||||
// Passes the call through, by attaching a callback to the wrapper provider promise.
|
||||
sendAsync(...all) {
|
||||
sendAsync(...all: Parameters<HDWalletProvider["sendAsync"]>): ReturnType<HDWalletProvider["sendAsync"]> {
|
||||
this.wrappedProviderPromise.then((wrappedProvider) => {
|
||||
wrappedProvider.sendAsync(...all);
|
||||
});
|
||||
}
|
||||
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `getOrConstructWrappedProvider`.
|
||||
send(...all) {
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `constructWrappedProvider`.
|
||||
send(...all: Parameters<HDWalletProvider["send"]>): ReturnType<HDWalletProvider["send"]> {
|
||||
this.wrappedProviderPromise.then((wrappedProvider) => {
|
||||
wrappedProvider.send(...all);
|
||||
});
|
||||
}
|
||||
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `getOrConstructWrappedProvider`.
|
||||
getAddress(...all) {
|
||||
// Passes the call through. Requires that the wrapped provider has been created via, e.g., `constructWrappedProvider`.
|
||||
getAddress(...all: Parameters<HDWalletProvider["getAddress"]>): ReturnType<HDWalletProvider["getAddress"]> {
|
||||
return this.getWrappedProviderOrThrow().getAddress(...all);
|
||||
}
|
||||
|
||||
// Returns the underlying wrapped provider.
|
||||
getWrappedProviderOrThrow() {
|
||||
getWrappedProviderOrThrow(): HDWalletProvider {
|
||||
if (this.wrappedProvider) {
|
||||
return this.wrappedProvider;
|
||||
} else {
|
||||
@@ -54,16 +66,14 @@ class ManagedSecretProvider {
|
||||
}
|
||||
|
||||
// Returns a Promise that resolves to the wrapped provider.
|
||||
async getOrConstructWrappedProvider() {
|
||||
async getOrConstructWrappedProvider(): Promise<HDWalletProvider> {
|
||||
if (this.wrappedProvider) {
|
||||
return this.wrappedProvider;
|
||||
}
|
||||
|
||||
const keys = await retrieveGckmsKeys(this.cloudKmsSecretConfigs);
|
||||
this.wrappedProvider = new HDWalletProvider(keys, ...this.remainingArgs);
|
||||
this.wrappedProvider = new HDWalletProvider(keys, this.providerOrUrl, ...this.remainingArgs);
|
||||
|
||||
return this.wrappedProvider;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ManagedSecretProvider };
|
||||
@@ -1,5 +1,6 @@
|
||||
const kms = require("@google-cloud/kms");
|
||||
const { Storage } = require("@google-cloud/storage");
|
||||
import kms from "@google-cloud/kms";
|
||||
import { Storage } from "@google-cloud/storage";
|
||||
import type { KeyConfig } from "./GckmsConfig";
|
||||
|
||||
// This function takes an array of GCKMS configs that are shaped as follows:
|
||||
// {
|
||||
@@ -12,7 +13,7 @@ const { Storage } = require("@google-cloud/storage");
|
||||
// }
|
||||
//
|
||||
// It returns an array of private keys that can be used to send transactions.
|
||||
async function retrieveGckmsKeys(gckmsConfigs) {
|
||||
export async function retrieveGckmsKeys(gckmsConfigs: KeyConfig[]): Promise<string[]> {
|
||||
return await Promise.all(
|
||||
gckmsConfigs.map(async (config) => {
|
||||
const storage = new Storage();
|
||||
@@ -26,7 +27,8 @@ async function retrieveGckmsKeys(gckmsConfigs) {
|
||||
const client = new kms.KeyManagementServiceClient();
|
||||
const name = client.cryptoKeyPath(config.projectId, config.locationId, config.keyRingId, config.cryptoKeyId);
|
||||
const [result] = await client.decrypt({ name, ciphertext });
|
||||
return "0x" + Buffer.from(result.plaintext, "base64").toString().trim();
|
||||
if (!result.plaintext || result.plaintext instanceof Uint8Array) throw new Error("result.plaintext wrong type");
|
||||
return "0x" + Buffer.from(result.plaintext.toString(), "base64").toString().trim();
|
||||
})
|
||||
);
|
||||
}
|
||||
+11
-9
@@ -1,13 +1,17 @@
|
||||
const { interfaceName } = require("../../Constants");
|
||||
const { RegistryRolesEnum } = require("../../Enums");
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { interfaceName } from "../../Constants";
|
||||
import { RegistryRolesEnum } from "../../Enums";
|
||||
import { CombinedHRE } from "../tasks/types";
|
||||
import { DeploymentsExtension } from "hardhat-deploy/types";
|
||||
|
||||
async function runDefaultFixture({ deployments }) {
|
||||
const setup = deployments.createFixture(async ({ deployments, getNamedAccounts, web3 }) => {
|
||||
export async function runDefaultFixture({ deployments }: { deployments: DeploymentsExtension }): Promise<void> {
|
||||
const setup = deployments.createFixture(async (hre: HardhatRuntimeEnvironment) => {
|
||||
const { deployments, getNamedAccounts, web3 } = hre as CombinedHRE; // Cast because hardhat extension isn't well-typed.
|
||||
const { padRight, toWei, utf8ToHex } = web3.utils;
|
||||
await deployments.fixture();
|
||||
const { deployer } = await getNamedAccounts();
|
||||
|
||||
const getDeployment = async (name) => {
|
||||
const getDeployment = async (name: string) => {
|
||||
const contract = await deployments.get(name);
|
||||
return new web3.eth.Contract(contract.abi, contract.address);
|
||||
};
|
||||
@@ -15,9 +19,9 @@ async function runDefaultFixture({ deployments }) {
|
||||
// Setup finder.
|
||||
const finder = await getDeployment("Finder");
|
||||
|
||||
const addToFinder = async (deploymentName, finderName) => {
|
||||
const addToFinder = async (deploymentName: string, finderName: string) => {
|
||||
const { address } = await deployments.get(deploymentName);
|
||||
const hexName = padRight(utf8ToHex(finderName));
|
||||
const hexName = padRight(utf8ToHex(finderName), 64);
|
||||
await finder.methods.changeImplementationAddress(hexName, address).send({ from: deployer });
|
||||
};
|
||||
|
||||
@@ -61,5 +65,3 @@ async function runDefaultFixture({ deployments }) {
|
||||
});
|
||||
await setup();
|
||||
}
|
||||
|
||||
module.exports = { runDefaultFixture };
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = { ...require("./default") };
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./default";
|
||||
@@ -1,6 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
["tasks"].forEach((folder) =>
|
||||
fs.readdirSync(path.join(__dirname, folder)).forEach((mod) => require(path.join(__dirname, folder, mod)))
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
["tasks"].forEach((folder) =>
|
||||
fs
|
||||
.readdirSync(path.join(__dirname, folder))
|
||||
.filter((path) => path.endsWith(".js"))
|
||||
.forEach((mod) => require(path.join(__dirname, folder, mod)))
|
||||
);
|
||||
+50
-6
@@ -27,9 +27,46 @@
|
||||
// eventReturnValues => eventReturnValues.someValueICareAbout === 5);
|
||||
//
|
||||
|
||||
const { extendEnvironment } = require("hardhat/config");
|
||||
import { extendEnvironment } from "hardhat/config";
|
||||
import type { HardhatRuntimeEnvironment, Artifact } from "hardhat/types";
|
||||
import type { ContractSendMethod, Contract, EventData } from "web3-eth-contract";
|
||||
import type Web3 from "web3";
|
||||
import type { DeploymentsExtension } from "hardhat-deploy/types";
|
||||
|
||||
extendEnvironment((hre) => {
|
||||
export interface ContractFactory extends Artifact {
|
||||
deployed: () => Promise<Contract>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
new: (...args: any[]) => ContractSendMethod;
|
||||
at: (address: string) => Contract;
|
||||
}
|
||||
|
||||
type FindEventFunction = (
|
||||
txnResult: { blockNumber: number },
|
||||
contract: Contract,
|
||||
eventName: string,
|
||||
fn: (eventValues: EventData["returnValues"]) => boolean
|
||||
) => Promise<{
|
||||
match: EventData | undefined;
|
||||
allEvents: EventData["returnValues"][];
|
||||
}>;
|
||||
|
||||
export interface Extension {
|
||||
_artifactCache: { [name: string]: Artifact };
|
||||
getContract: (name: string) => ContractFactory;
|
||||
findEvent: FindEventFunction;
|
||||
assertEventEmitted: (...args: Parameters<FindEventFunction>) => void;
|
||||
assertEventNotEmitted: (...args: Parameters<FindEventFunction>) => void;
|
||||
}
|
||||
|
||||
interface OtherExtensions {
|
||||
web3: Web3;
|
||||
deployments: DeploymentsExtension;
|
||||
}
|
||||
|
||||
type HRE = Extension & OtherExtensions & HardhatRuntimeEnvironment;
|
||||
|
||||
extendEnvironment((_hre) => {
|
||||
const hre = _hre as HRE;
|
||||
hre._artifactCache = {};
|
||||
hre.getContract = (name) => {
|
||||
if (!hre._artifactCache[name]) hre._artifactCache[name] = hre.artifacts.readArtifactSync(name);
|
||||
@@ -40,15 +77,20 @@ extendEnvironment((hre) => {
|
||||
return new hre.web3.eth.Contract(artifact.abi, deployment.address);
|
||||
};
|
||||
|
||||
const newProp = (...args) =>
|
||||
const newProp = (...args: any[]) =>
|
||||
new hre.web3.eth.Contract(artifact.abi, undefined).deploy({ data: artifact.bytecode, arguments: args });
|
||||
|
||||
const at = (address) => new hre.web3.eth.Contract(artifact.abi, address);
|
||||
const at = (address: string) => new hre.web3.eth.Contract(artifact.abi, address);
|
||||
|
||||
return { ...artifact, deployed, new: newProp, at };
|
||||
};
|
||||
|
||||
hre.findEvent = async (txnResult, contract, eventName, fn = () => true) => {
|
||||
hre.findEvent = async (
|
||||
txnResult,
|
||||
contract,
|
||||
eventName,
|
||||
fn: (eventValues: EventData["returnValues"]) => boolean = () => true
|
||||
) => {
|
||||
// TODO: this can be improved by making sure the event falls in the correct transaction.
|
||||
const events = await contract.getPastEvents(eventName, {
|
||||
fromBlock: txnResult.blockNumber,
|
||||
@@ -65,7 +107,9 @@ extendEnvironment((hre) => {
|
||||
const { match, allEvents } = await hre.findEvent(txnResult, contract, eventName, fn);
|
||||
|
||||
if (match === undefined) {
|
||||
throw new Error(`No matching events found. Events found:\n\n${allEvents.map(JSON.stringify).join("\n\n")}`);
|
||||
throw new Error(
|
||||
`No matching events found. Events found:\n\n${allEvents.map((event) => JSON.stringify(event)).join("\n\n")}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+19
-17
@@ -1,10 +1,11 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const uniqBy = require("lodash.uniqby");
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
import { task, types } from "hardhat/config";
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { CombinedHRE } from "./types";
|
||||
|
||||
const { task, types } = require("hardhat/config");
|
||||
|
||||
function removeFileIfExists(filename) {
|
||||
function removeFileIfExists(filename: string): void {
|
||||
try {
|
||||
fs.unlinkSync(filename);
|
||||
} catch (e) {
|
||||
@@ -12,18 +13,18 @@ function removeFileIfExists(filename) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeClassName(name) {
|
||||
function normalizeClassName(name: string): string {
|
||||
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1); // Capitalize first letter.
|
||||
return capitalizedName.replace(/_/g, ""); // Remove underscores.
|
||||
}
|
||||
|
||||
async function getArtifactPathList(hre, relativeTo) {
|
||||
async function getArtifactPathList(hre: HardhatRuntimeEnvironment, relativeTo: string) {
|
||||
const artifactPaths = await hre.artifacts.getArtifactPaths();
|
||||
|
||||
// Generate a unique list of artifacts and paths to them. Unique is necessary because there are some redundantly
|
||||
// named contracts.
|
||||
return uniqBy(
|
||||
artifactPaths.map((artifactPath) => ({
|
||||
artifactPaths.map((artifactPath: string) => ({
|
||||
contractName: path.basename(artifactPath).split(".")[0],
|
||||
relativePath: `./${path.relative(path.dirname(relativeTo), artifactPath)}`,
|
||||
})),
|
||||
@@ -31,16 +32,16 @@ async function getArtifactPathList(hre, relativeTo) {
|
||||
);
|
||||
}
|
||||
|
||||
function getCorePath(hre, relativeTo) {
|
||||
function getCorePath(hre: HardhatRuntimeEnvironment, relativeTo: string): string {
|
||||
const artifactPath = hre.config.paths.artifacts ? path.join(hre.config.paths.artifacts, "../") : "./";
|
||||
return `./${path.relative(relativeTo, artifactPath)}`;
|
||||
}
|
||||
|
||||
function getAddressesMap(hre) {
|
||||
function getAddressesMap(hre: HardhatRuntimeEnvironment) {
|
||||
// Generate a map of name => chain id => address.
|
||||
const networksPath = path.join(getCorePath(hre, "./"), "networks");
|
||||
const dirs = fs.readdirSync(networksPath);
|
||||
const addresses = {};
|
||||
const addresses: { [name: string]: { [chainId: number]: string } } = {};
|
||||
for (const dir of dirs) {
|
||||
const chainId = parseInt(dir.split(".")[0]);
|
||||
const deployments = JSON.parse(fs.readFileSync(path.join(networksPath, dir), "utf8"));
|
||||
@@ -110,11 +111,11 @@ task("generate-contracts-frontend", "Generate typescipt for the contracts-fronte
|
||||
// to remove any unused json files. In modern versions of webpack, this should allow absolutely _no_ artifact
|
||||
// information that isn't needed to be pulled in.
|
||||
artifacts.forEach(({ contractName, relativePath }) => {
|
||||
const abi = JSON.stringify(JSON.parse(fs.readFileSync(relativePath)).abi);
|
||||
const abi = JSON.stringify(JSON.parse(fs.readFileSync(relativePath).toString("utf8")).abi);
|
||||
fs.appendFileSync(out, `export function get${contractName}Abi(): any[] { return JSON.parse(\`${abi}\`); }\n`);
|
||||
});
|
||||
artifacts.forEach(({ contractName, relativePath }) => {
|
||||
const bytecode = JSON.stringify(JSON.parse(fs.readFileSync(relativePath)).bytecode);
|
||||
const bytecode = JSON.stringify(JSON.parse(fs.readFileSync(relativePath).toString("utf8")).bytecode);
|
||||
fs.appendFileSync(out, `export function get${contractName}Bytecode(): string { return \`${bytecode}\`; }\n`);
|
||||
});
|
||||
|
||||
@@ -238,11 +239,12 @@ export async function getAddress(name: DeploymentName | ContractName, chainId: n
|
||||
});
|
||||
|
||||
task("load-addresses", "Load addresses from the networks folder into the hardhat deployments folder").setAction(
|
||||
async function (taskArguments, hre) {
|
||||
async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
// Generate chain id mapping.
|
||||
const chainIdToNetworkName = {};
|
||||
const chainIdToNetworkName: { [chainId: number]: string } = {};
|
||||
for (const [name, { chainId }] of Object.entries(hre.config.networks)) {
|
||||
chainIdToNetworkName[chainId] = name;
|
||||
if (chainId !== undefined) chainIdToNetworkName[chainId] = name;
|
||||
}
|
||||
|
||||
const dirs = fs.readdirSync("./networks");
|
||||
+7
-5
@@ -1,4 +1,5 @@
|
||||
const { task, types } = require("hardhat/config");
|
||||
import { task, types } from "hardhat/config";
|
||||
import type { CombinedHRE } from "./types";
|
||||
|
||||
// Use BLANK_FUNCTION_SIG if you don't want `deposit` or `executeProposal` to delegate a contract call.
|
||||
const BLANK_FUNCTION_SIG = "0x00000000";
|
||||
@@ -17,7 +18,8 @@ task("register-generic-resource", "Admin can set generic resource ID on Bridge")
|
||||
)
|
||||
.addOptionalParam("deposit", "Deposit function prototype string (e.g. func(uint256,bool))", "", types.string)
|
||||
.addOptionalParam("execute", "Contract to delegate call to for this resource ID", "", types.string)
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE; // Cast to the extended HRE.
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
const { target, deposit, execute, rname, cid } = taskArguments;
|
||||
@@ -26,11 +28,11 @@ task("register-generic-resource", "Admin can set generic resource ID on Bridge")
|
||||
const { utf8ToHex, sha3 } = web3.utils;
|
||||
|
||||
// Returns first 4 bytes of the sha3 hash of the function name including types/
|
||||
const _getFunctionSignature = (functionPrototypeString) => {
|
||||
return sha3(utf8ToHex(functionPrototypeString)).substr(0, 10);
|
||||
const _getFunctionSignature = (functionPrototypeString: string) => {
|
||||
return sha3(utf8ToHex(functionPrototypeString))?.substr(0, 10);
|
||||
};
|
||||
|
||||
const _getResourceId = (name, chainId) => {
|
||||
const _getResourceId = (name: string, chainId: string) => {
|
||||
const encodedParams = web3.eth.abi.encodeParameters(["string", "uint8"], [name, chainId]);
|
||||
return web3.utils.soliditySha3(encodedParams);
|
||||
};
|
||||
+11
-6
@@ -1,9 +1,14 @@
|
||||
const { getAllFilesInPath } = require("@uma/common");
|
||||
import { getAllFilesInPath } from "../../FileHelpers";
|
||||
|
||||
// This file is mostly taken from the modified `compile` task file written by Synthetix: https://github.com/Synthetixio/synthetix
|
||||
|
||||
const { internalTask } = require("hardhat/config");
|
||||
const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require("hardhat/builtin-tasks/task-names");
|
||||
import { internalTask } from "hardhat/config";
|
||||
import type { HttpNetworkConfig } from "hardhat/types";
|
||||
import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names";
|
||||
|
||||
interface ExtendedConfig extends HttpNetworkConfig {
|
||||
compileWhitelist?: string[];
|
||||
}
|
||||
|
||||
// This overrides a hardhat internal task, which is part of its compile task's lifecycle.
|
||||
// This allows us to filter on whitelisted, OVM-compatible contracts from the compilation list,
|
||||
@@ -23,10 +28,10 @@ internalTask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, async (_, { config, network
|
||||
filePaths = [...filePaths, ...getAllFilesInPath(`${corePath}/contracts-ovm`)];
|
||||
|
||||
// Build absolute path for all directories on user-specified whitelist.
|
||||
const whitelist = config.networks[network.name].compileWhitelist;
|
||||
const whitelist = (config.networks[network.name] as ExtendedConfig).compileWhitelist;
|
||||
if (whitelist && Array.isArray(whitelist)) {
|
||||
filePaths = filePaths.filter((filePath) => {
|
||||
for (let whitelistedDir of whitelist) {
|
||||
filePaths = filePaths.filter((filePath: string) => {
|
||||
for (const whitelistedDir of whitelist) {
|
||||
if (filePath.includes(whitelistedDir)) return true;
|
||||
else continue;
|
||||
}
|
||||
+13
-4
@@ -1,5 +1,12 @@
|
||||
const { task } = require("hardhat/config");
|
||||
const { interfaceName } = require("@uma/common");
|
||||
import { task } from "hardhat/config";
|
||||
import { interfaceName } from "../../Constants";
|
||||
import assert from "assert";
|
||||
import type { CombinedHRE } from "./types";
|
||||
|
||||
type InterfaceName = keyof typeof interfaceName;
|
||||
function isInterfaceName(name: string | InterfaceName): name is InterfaceName {
|
||||
return name in interfaceName;
|
||||
}
|
||||
|
||||
task("setup-finder", "Points Finder to DVM system contracts")
|
||||
.addFlag("registry", "Use if you want to set Registry")
|
||||
@@ -14,7 +21,8 @@ task("setup-finder", "Points Finder to DVM system contracts")
|
||||
.addFlag("sinkoracle", "Use if you want to set SinkOracle as the Oracle")
|
||||
.addFlag("prod", "Configure production setup in Finder")
|
||||
.addFlag("test", "Configure test setup in Finder")
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { padRight, utf8ToHex } = web3.utils;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
@@ -62,9 +70,10 @@ task("setup-finder", "Points Finder to DVM system contracts")
|
||||
const Finder = await deployments.get("Finder");
|
||||
const finder = new web3.eth.Contract(Finder.abi, Finder.address);
|
||||
console.log(`Using Finder @ ${finder.options.address}`);
|
||||
for (let contractName of contractsToSet) {
|
||||
for (const contractName of contractsToSet) {
|
||||
const deployed = await deployments.get(contractName);
|
||||
const contract = new web3.eth.Contract(deployed.abi, deployed.address);
|
||||
if (!isInterfaceName(contractName)) throw new Error(`No mapped interface name for contract name ${contractName}`);
|
||||
const identifierHex = padRight(utf8ToHex(interfaceName[contractName]), 64);
|
||||
const currentlySetAddress = await finder.methods.interfacesImplemented(identifierHex).call();
|
||||
if (currentlySetAddress !== contract.options.address) {
|
||||
+10
-4
@@ -1,6 +1,7 @@
|
||||
const { task, types } = require("hardhat/config");
|
||||
const MaticJs = require("@maticnetwork/maticjs");
|
||||
require("dotenv").config();
|
||||
import { task, types } from "hardhat/config";
|
||||
import MaticJs from "@maticnetwork/maticjs";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// In order to receive a message on Ethereum from Polygon, `receiveMessage` must be called on the Root Tunnel contract
|
||||
// with a proof derived from the Polygon transaction hash that was checkpointed to Mainnet.
|
||||
@@ -22,9 +23,14 @@ task("root-chain-manager-proof", "Generate proof needed to receive data from roo
|
||||
? `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`
|
||||
: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
||||
});
|
||||
|
||||
// Note: we use a private member of maticPosClient, so we cast to get rid of the error.
|
||||
const castedMaticPOSClient = (maticPOSClient as unknown) as {
|
||||
posRootChainManager: typeof maticPOSClient["posRootChainManager"];
|
||||
};
|
||||
// This method will fail if the Polygon transaction hash has not been checkpointed to Mainnet yet. Checkpoints
|
||||
// happen roughly every hour.
|
||||
const proof = await maticPOSClient.posRootChainManager.customPayload(
|
||||
const proof = await castedMaticPOSClient.posRootChainManager.customPayload(
|
||||
hash,
|
||||
"0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036" // SEND_MESSAGE_EVENT_SIG, do not change
|
||||
);
|
||||
+36
-14
@@ -1,9 +1,18 @@
|
||||
const { task, types } = require("hardhat/config");
|
||||
require("dotenv").config();
|
||||
const assert = require("assert");
|
||||
const Web3 = require("web3");
|
||||
import { task, types } from "hardhat/config";
|
||||
import assert from "assert";
|
||||
import Web3 from "web3";
|
||||
import dotenv from "dotenv";
|
||||
import type { Contract } from "web3-eth-contract";
|
||||
import type { TransactionReceipt } from "web3-core";
|
||||
import type { CombinedHRE } from "./types";
|
||||
dotenv.config();
|
||||
|
||||
const _whitelistIdentifier = async (web3, identifierUtf8, identifierWhitelist, deployer) => {
|
||||
const _whitelistIdentifier = async (
|
||||
web3: Web3,
|
||||
identifierUtf8: string,
|
||||
identifierWhitelist: Contract,
|
||||
deployer: string
|
||||
) => {
|
||||
const { padRight, utf8ToHex } = web3.utils;
|
||||
const identifierBytes = padRight(utf8ToHex(identifierUtf8), 64);
|
||||
if (!(await identifierWhitelist.methods.isIdentifierSupported(identifierBytes).call())) {
|
||||
@@ -14,9 +23,14 @@ const _whitelistIdentifier = async (web3, identifierUtf8, identifierWhitelist, d
|
||||
}
|
||||
};
|
||||
|
||||
function isString(input: string | null): input is string {
|
||||
return typeof input === "string";
|
||||
}
|
||||
|
||||
task("whitelist-identifiers", "Whitelist identifiers from JSON file")
|
||||
.addParam("id", "Custom identifier to whitelist", "Test Identifier", types.string)
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
const { id } = taskArguments;
|
||||
@@ -37,7 +51,8 @@ task("migrate-identifiers", "Adds all whitelisted identifiers on one IdentifierW
|
||||
false,
|
||||
types.boolean
|
||||
)
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
const { from, to, crosschain } = taskArguments;
|
||||
@@ -61,35 +76,42 @@ task("migrate-identifiers", "Adds all whitelisted identifiers on one IdentifierW
|
||||
|
||||
// Filter out identifiers that are not currently whitelisted.
|
||||
const isIdentifierSupported = await Promise.all(
|
||||
addedIdentifierEvents.map((_event) =>
|
||||
addedIdentifierEvents.map((_event): boolean =>
|
||||
oldWhitelist.methods.isIdentifierSupported(_event.returnValues.identifier).call()
|
||||
)
|
||||
);
|
||||
const identifiersToWhitelist = isIdentifierSupported
|
||||
.map((isOnWhitelist, i) => {
|
||||
if (isOnWhitelist) return addedIdentifierEvents[i].returnValues.identifier;
|
||||
// Cast to help typescript discern the type.
|
||||
if (isOnWhitelist) return addedIdentifierEvents[i].returnValues.identifier as string;
|
||||
return null;
|
||||
})
|
||||
.filter((id) => id);
|
||||
.filter(isString);
|
||||
|
||||
interface TableElement {
|
||||
identifierToWhitelist: string;
|
||||
utf8: string;
|
||||
txn?: string;
|
||||
}
|
||||
|
||||
// Create table with results to display to user:
|
||||
let resultsTable = identifiersToWhitelist.map((id) => {
|
||||
const resultsTable: TableElement[] = identifiersToWhitelist.map((id) => {
|
||||
return { identifierToWhitelist: id, utf8: web3.utils.hexToUtf8(id) };
|
||||
});
|
||||
|
||||
if (to) {
|
||||
const newWhitelist = new web3.eth.Contract(IdentifierWhitelist.abi, to);
|
||||
const isIdentifierSupportedOnNewWhitelist = await Promise.all(
|
||||
identifiersToWhitelist.map((id) => newWhitelist.methods.isIdentifierSupported(id).call())
|
||||
identifiersToWhitelist.map((id) => newWhitelist.methods.isIdentifierSupported(id).call() as boolean)
|
||||
);
|
||||
|
||||
// Send transactions sequentially to avoid nonce collisions. Note that this might fail due to timeout if there
|
||||
// are a lot of transactions to send or the gas price to send with is too low.
|
||||
for (let i = 0; i < isIdentifierSupportedOnNewWhitelist.length; i++) {
|
||||
if (!isIdentifierSupportedOnNewWhitelist[i]) {
|
||||
const receipt = await newWhitelist.methods
|
||||
const receipt = (await newWhitelist.methods
|
||||
.addSupportedIdentifier(identifiersToWhitelist[i])
|
||||
.send({ from: deployer });
|
||||
.send({ from: deployer })) as TransactionReceipt;
|
||||
console.log(
|
||||
`${i}: Added new identifier ${web3.utils.hexToUtf8(identifiersToWhitelist[i])} (${receipt.transactionHash})`
|
||||
);
|
||||
+8
-4
@@ -1,11 +1,13 @@
|
||||
const { task, types } = require("hardhat/config");
|
||||
import { task, types } from "hardhat/config";
|
||||
import { CombinedHRE } from "./types";
|
||||
|
||||
task("check-price", "Check whether price has resolved for Oracle and return price")
|
||||
.addParam("oracle", "OracleInterface implementation address to check", undefined, types.string)
|
||||
.addParam("identifier", "Request identifier", undefined, types.string)
|
||||
.addParam("timestamp", "Request timestamp", undefined, types.string)
|
||||
.addParam("ancillary", "Request ancillary data", undefined, types.string)
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
const { oracle, identifier, timestamp, ancillary } = taskArguments;
|
||||
@@ -14,13 +16,15 @@ task("check-price", "Check whether price has resolved for Oracle and return pric
|
||||
const registry = new web3.eth.Contract(Registry.abi, Registry.address);
|
||||
console.log(`Checking Registry @ ${registry.options.address}`);
|
||||
|
||||
const isRegistered = await registry.methods.isContractRegistered(deployer).call();
|
||||
const isRegistered = (await registry.methods.isContractRegistered(deployer).call()) as boolean;
|
||||
if (!isRegistered) {
|
||||
console.log("Caller not registered");
|
||||
} else {
|
||||
const Oracle = await deployments.get("OracleAncillaryInterface");
|
||||
const oracleContract = new web3.eth.Contract(Oracle.abi, oracle);
|
||||
const hasPrice = await oracleContract.methods.hasPrice(identifier, timestamp, ancillary).call({ from: deployer });
|
||||
const hasPrice = (await oracleContract.methods
|
||||
.hasPrice(identifier, timestamp, ancillary)
|
||||
.call({ from: deployer })) as boolean;
|
||||
console.log(`Oracle @ ${oracle} ${hasPrice ? "has" : "does not have"} a price`);
|
||||
if (hasPrice) {
|
||||
const price = await oracleContract.methods.getPrice(identifier, timestamp, ancillary).call({ from: deployer });
|
||||
+7
-4
@@ -1,7 +1,9 @@
|
||||
const { task, types } = require("hardhat/config");
|
||||
const { RegistryRolesEnum } = require("../../Enums");
|
||||
import { task, types } from "hardhat/config";
|
||||
import { RegistryRolesEnum } from "../../Enums";
|
||||
import { Contract } from "web3-eth-contract";
|
||||
import { CombinedHRE } from "./types";
|
||||
|
||||
const _registerAccount = async (account, registry, deployer) => {
|
||||
const _registerAccount = async (account: string, registry: Contract, deployer: string) => {
|
||||
const isRegistered = await registry.methods.isContractRegistered(account).call();
|
||||
if (!isRegistered) {
|
||||
console.log(`Registering ${account}...`);
|
||||
@@ -14,7 +16,8 @@ const _registerAccount = async (account, registry, deployer) => {
|
||||
|
||||
task("register-accounts", "Register deployer plus custom account with Registry capable of making price requests")
|
||||
.addOptionalParam("account", "Custom account to register", "", types.string)
|
||||
.setAction(async function (taskArguments, hre) {
|
||||
.setAction(async function (taskArguments, hre_) {
|
||||
const hre = hre_ as CombinedHRE;
|
||||
const { deployments, getNamedAccounts, web3 } = hre;
|
||||
const { deployer } = await getNamedAccounts();
|
||||
const { account } = taskArguments;
|
||||
+16
-8
@@ -1,5 +1,11 @@
|
||||
const { internalTask } = require("hardhat/config");
|
||||
const { TASK_TEST_GET_TEST_FILES } = require("hardhat/builtin-tasks/task-names");
|
||||
import { internalTask } from "hardhat/config";
|
||||
import type { HttpNetworkConfig } from "hardhat/types";
|
||||
import { TASK_TEST_GET_TEST_FILES } from "hardhat/builtin-tasks/task-names";
|
||||
|
||||
interface ExtendedNetworkConfig extends HttpNetworkConfig {
|
||||
testBlacklist?: string[];
|
||||
testWhitelist?: string[];
|
||||
}
|
||||
|
||||
// This overrides a hardhat internal task, which is part of its test task's lifecycle. This allows us to only run tests
|
||||
// that are compatible with a given network config, which are described by entries in a hardhat network's
|
||||
@@ -12,11 +18,13 @@ const { TASK_TEST_GET_TEST_FILES } = require("hardhat/builtin-tasks/task-names")
|
||||
internalTask(TASK_TEST_GET_TEST_FILES, async (_, { config, network }, runSuper) => {
|
||||
let filePaths = await runSuper();
|
||||
|
||||
const networkConfig = config.networks[network.name] as ExtendedNetworkConfig; // Cast to allow extra props.
|
||||
|
||||
// Build absolute path for all directories on user-specified whitelist.
|
||||
const whitelist = config.networks[network.name].testWhitelist;
|
||||
const whitelist = networkConfig.testWhitelist;
|
||||
if (whitelist && Array.isArray(whitelist)) {
|
||||
filePaths = filePaths.filter((filePath) => {
|
||||
for (let whitelistString of whitelist) {
|
||||
filePaths = filePaths.filter((filePath: string) => {
|
||||
for (const whitelistString of whitelist) {
|
||||
if (filePath.includes(whitelistString)) return true;
|
||||
else continue;
|
||||
}
|
||||
@@ -25,10 +33,10 @@ internalTask(TASK_TEST_GET_TEST_FILES, async (_, { config, network }, runSuper)
|
||||
}
|
||||
|
||||
// Some tests should not be run using hardhat. Define a `testBlacklist`. Ignore any tests that contain the blacklist.
|
||||
const blacklist = config.networks[network.name].testBlacklist;
|
||||
const blacklist = networkConfig.testBlacklist;
|
||||
if (blacklist && Array.isArray(blacklist)) {
|
||||
filePaths = filePaths.filter((filePath) => {
|
||||
for (let blacklistedString of blacklist) {
|
||||
filePaths = filePaths.filter((filePath: string) => {
|
||||
for (const blacklistedString of blacklist) {
|
||||
if (filePath.includes(blacklistedString)) return false;
|
||||
else continue;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { DeploymentsExtension } from "hardhat-deploy/types";
|
||||
import type { HardhatRuntimeEnvironment } from "hardhat/types/runtime";
|
||||
import type { EthereumProvider } from "hardhat/types";
|
||||
import type { Extension as ExtendedWeb3 } from "../plugins/ExtendedWeb3";
|
||||
import type Web3 from "web3";
|
||||
|
||||
type Address = string;
|
||||
|
||||
interface DeployExtension {
|
||||
deployments: DeploymentsExtension;
|
||||
getNamedAccounts: () => Promise<{
|
||||
[name: string]: Address;
|
||||
}>;
|
||||
getUnnamedAccounts: () => Promise<Address[]>;
|
||||
getChainId(): Promise<string>;
|
||||
companionNetworks: {
|
||||
[name: string]: {
|
||||
deployments: DeploymentsExtension;
|
||||
getNamedAccounts: () => Promise<{
|
||||
[name: string]: Address;
|
||||
}>;
|
||||
getUnnamedAccounts: () => Promise<string[]>;
|
||||
getChainId(): Promise<string>;
|
||||
provider: EthereumProvider;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Assumes this includes the normal HRE, hardhat-deploy extension, the ExtendedWeb3 extension and the web3 extension.
|
||||
// TODO: figure out how to get the extended HRE from hardhat-deploy.
|
||||
export type CombinedHRE = HardhatRuntimeEnvironment &
|
||||
DeployExtension &
|
||||
ExtendedWeb3 & {
|
||||
web3: Web3;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from "./browser";
|
||||
export * from "./gckms/ManagedSecretProvider";
|
||||
export * from "./FileHelpers";
|
||||
export * from "./MetaMaskTruffleProvider";
|
||||
export * from "./TruffleConfig";
|
||||
export * from "./ProviderUtils";
|
||||
export * from "./HardhatConfig";
|
||||
export * from "./RetryProvider";
|
||||
export * from "./UniswapV3Helpers";
|
||||
@@ -0,0 +1,3 @@
|
||||
import type _Web3 from "web3";
|
||||
|
||||
export type BN = ReturnType<_Web3["utils"]["toBN"]>;
|
||||
@@ -1,5 +1,5 @@
|
||||
// Script to test
|
||||
const TransactionUtils = require("../src/TransactionUtils");
|
||||
const TransactionUtils = require("../dist/TransactionUtils");
|
||||
const { getTruffleContract, getAbi } = require("@uma/core");
|
||||
const ERC20 = getTruffleContract("BasicERC20", web3);
|
||||
const ERC20ABI = getAbi("BasicERC20");
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"extends": "@tsconfig/node14/tsconfig.json",
|
||||
"include": ["src/**/*", "index.js"],
|
||||
"include": ["src/**/*", "index.ts"],
|
||||
"exclude": ["dist/**/*"],
|
||||
"compilerOptions": {
|
||||
"typeRoots": ["./types", "./node_modules/@types"],
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"allowJs": true
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
declare module "abi-decoder" {
|
||||
export function addABI(abi: any | any[]): void;
|
||||
export function decodeMethod(data: string): { name: string; params: any };
|
||||
export function getMethodids(): { [signature: string]: any };
|
||||
export function getABIs(): any[];
|
||||
export function decodeLogs(logs: any[]): { name: string; events: any[]; address: string }[];
|
||||
export function removeABI(abi: any | any[]): void;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
|
||||
declare module "node-metamask" {
|
||||
|
||||
interface JsonRpcPayload {
|
||||
jsonrpc: string;
|
||||
method: string;
|
||||
params: any[];
|
||||
id?: string | number;
|
||||
}
|
||||
|
||||
interface JsonRpcResponse {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
result?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class RemoteMetaMaskProvider {
|
||||
constructor(metamaskConnector: MetamaskConnector);
|
||||
sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void;
|
||||
send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void;
|
||||
}
|
||||
|
||||
export default class MetamaskConnector {
|
||||
constructor(options: any);
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<boolean>;
|
||||
ready(): boolean;
|
||||
send(action: any, requestId: any, payload: any, requiredAction: any): Promise<{ requestId: any, result: any }>;
|
||||
getProvider(): RemoteMetaMaskProvider;
|
||||
static handleAction(action: any, requestId: any, payload: any): { responseAction: any; responseRequestId: any, responsePayload: any };
|
||||
static handleMessage(msg: string): ReturnType<MetaMaskConnector["handleAction"]>;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
declare module "@umaprotocol/truffle-ledger-provider" {
|
||||
interface JsonRpcPayload {
|
||||
jsonrpc: string;
|
||||
method: string;
|
||||
params: any[];
|
||||
id?: string | number;
|
||||
}
|
||||
|
||||
interface JsonRpcResponse {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
result?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export default class TruffleLedgerProvider {
|
||||
constructor(options: any, urlOrProvider: any);
|
||||
sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void;
|
||||
send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
declare module "@umaprotocol/ynatm" {
|
||||
export const EXPONENTIAL: (base?: number, inGwei?: boolean) => (arg: { x?: number }) => number;
|
||||
export const LINEAR: (slope?: number, inGwei?: boolean) => (arg: { x?: number, c?: number }) => number;
|
||||
export const DOUBLES: (arg: { y?: number }) => number;
|
||||
export const toGwei: (x: number) => number;
|
||||
|
||||
export interface SendArgs<T> {
|
||||
sendTransactionFunction: (gasPrice: number) => T;
|
||||
minGasPrice: string | number;
|
||||
maxGasPrice: string | number;
|
||||
gasPriceScalingFunction?: (args: { x?: number, y?: number, c?: number }) => number;
|
||||
delay?: number;
|
||||
rejectImmediatelyOnCondition?: (e: Error) => boolean;
|
||||
}
|
||||
|
||||
export const send: <T>(args: SendArgs<T>) => T;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
const DesignatedVotingFactory = artifacts.require("DesignatedVotingFactory");
|
||||
const Finder = artifacts.require("Finder");
|
||||
const { getKeysForNetwork, deploy } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -2,13 +2,8 @@ const OptimisticOracle = artifacts.require("OptimisticOracle");
|
||||
const Finder = artifacts.require("Finder");
|
||||
const Timer = artifacts.require("Timer");
|
||||
const Registry = artifacts.require("Registry");
|
||||
const {
|
||||
getKeysForNetwork,
|
||||
deploy,
|
||||
enableControllableTiming,
|
||||
interfaceName,
|
||||
RegistryRolesEnum,
|
||||
} = require("@uma/common");
|
||||
const { interfaceName, RegistryRolesEnum } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const IdentiferWhitelist = artifacts.require("IdentifierWhitelist");
|
||||
const { getKeysForNetwork } = require("@uma/common");
|
||||
const { getKeysForNetwork } = require("./MigrationUtils");
|
||||
const identifiers = require("../config/identifiers");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const TestnetERC20 = artifacts.require("TestnetERC20");
|
||||
const { deploy, setToExistingAddress, getKeysForNetwork, PublicNetworks } = require("@uma/common");
|
||||
const { PublicNetworks } = require("@uma/common");
|
||||
const { deploy, setToExistingAddress, getKeysForNetwork } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const TokenFactory = artifacts.require("TokenFactory");
|
||||
const { getKeysForNetwork, deploy } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -6,13 +6,8 @@ const TokenFactory = artifacts.require("TokenFactory");
|
||||
const Timer = artifacts.require("Timer");
|
||||
const Registry = artifacts.require("Registry");
|
||||
const TestnetERC20 = artifacts.require("TestnetERC20");
|
||||
const {
|
||||
RegistryRolesEnum,
|
||||
interfaceName,
|
||||
getKeysForNetwork,
|
||||
deploy,
|
||||
enableControllableTiming,
|
||||
} = require("@uma/common");
|
||||
const { interfaceName, RegistryRolesEnum } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const WETH9 = artifacts.require("WETH9");
|
||||
const AddressWhitelist = artifacts.require("AddressWhitelist");
|
||||
const { deploy, setToExistingAddress, getKeysForNetwork, PublicNetworks } = require("@uma/common");
|
||||
const { PublicNetworks } = require("@uma/common");
|
||||
const { deploy, setToExistingAddress, getKeysForNetwork } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -4,7 +4,8 @@ const PerpetualLib = artifacts.require("PerpetualLib");
|
||||
const TokenFactory = artifacts.require("TokenFactory");
|
||||
const Timer = artifacts.require("Timer");
|
||||
const Registry = artifacts.require("Registry");
|
||||
const { RegistryRolesEnum, getKeysForNetwork, deploy, enableControllableTiming } = require("@uma/common");
|
||||
const { RegistryRolesEnum } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Migrations = artifacts.require("./Migrations.sol");
|
||||
const { getKeysForNetwork, deploy } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Finder = artifacts.require("Finder");
|
||||
const { getKeysForNetwork, deploy } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Timer = artifacts.require("Timer");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -5,7 +5,7 @@ const argv = require("minimist")(process.argv.slice(), { string: ["token_convers
|
||||
|
||||
const VotingToken = artifacts.require("VotingToken");
|
||||
const TokenMigrator = artifacts.require("TokenMigrator");
|
||||
const { getKeysForNetwork, deploy } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
const minterRoleEnumValue = 1;
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ const Voting = artifacts.require("Voting");
|
||||
const VotingToken = artifacts.require("VotingToken");
|
||||
const IdentifierWhitelist = artifacts.require("IdentifierWhitelist");
|
||||
const Timer = artifacts.require("Timer");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming, interfaceName } = require("@uma/common");
|
||||
const { interfaceName } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Finder = artifacts.require("Finder");
|
||||
const Registry = artifacts.require("Registry");
|
||||
const { getKeysForNetwork, deploy, interfaceName } = require("@uma/common");
|
||||
const { interfaceName } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const FinancialContractsAdmin = artifacts.require("FinancialContractsAdmin");
|
||||
const Finder = artifacts.require("Finder");
|
||||
const { getKeysForNetwork, deploy, interfaceName } = require("@uma/common");
|
||||
const { interfaceName } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -2,7 +2,8 @@ const Finder = artifacts.require("Finder");
|
||||
const Store = artifacts.require("Store");
|
||||
const Timer = artifacts.require("Timer");
|
||||
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming, interfaceName } = require("@uma/common");
|
||||
const { interfaceName } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -2,7 +2,8 @@ const Governor = artifacts.require("Governor");
|
||||
const Finder = artifacts.require("Finder");
|
||||
const Registry = artifacts.require("Registry");
|
||||
const Timer = artifacts.require("Timer");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming, RegistryRolesEnum } = require("@uma/common");
|
||||
const { RegistryRolesEnum } = require("@uma/common");
|
||||
const { getKeysForNetwork, deploy, enableControllableTiming } = require("./MigrationUtils");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const keys = getKeysForNetwork(network, accounts);
|
||||
|
||||
@@ -11,7 +11,7 @@ const tdr = require("truffle-deploy-registry");
|
||||
const argv = require("minimist")(process.argv.slice(), { boolean: ["keep_finder", "keep_token", "keep_system"] });
|
||||
|
||||
// Grab the name property from each to get a list of the names of the public networks.
|
||||
const publicNetworkNames = Object.values(require("./PublicNetworks.js").PublicNetworks).map((elt) => elt.name);
|
||||
const publicNetworkNames = Object.values(require("@uma/common").PublicNetworks).map((elt) => elt.name);
|
||||
|
||||
function isPublicNetwork(network) {
|
||||
return publicNetworkNames.some((name) => network.startsWith(name));
|
||||
@@ -67,7 +67,7 @@ async function main() {
|
||||
const checkSumRecipientAddress = toChecksumAddress(recipientAddress); // Ensure consistent address case
|
||||
|
||||
// Scale the amount by the number of decimals for that particular input.
|
||||
const recipientAmountScaled = ConvertDecimals(0, o.decimals[i], Web3)(recipients[recipientAddress]);
|
||||
const recipientAmountScaled = ConvertDecimals(0, o.decimals[i])(recipients[recipientAddress]);
|
||||
|
||||
// If the output file already contains information for this particular recipient, then append and add their rewards.
|
||||
// Else, simply init the object with their values from the file. Note that accountIndex in both cases is set to -1.
|
||||
|
||||
@@ -41,7 +41,7 @@ export class RangeTrader {
|
||||
assert(tokenPriceFeed.getPriceFeedDecimals() === referencePriceFeed.getPriceFeedDecimals(), "decimals must match");
|
||||
assert(exchangeAdapter, "Exchange adapter must be initialized");
|
||||
|
||||
this.normalizePriceFeedDecimals = ConvertDecimals(tokenPriceFeed.getPriceFeedDecimals(), 18, this.web3);
|
||||
this.normalizePriceFeedDecimals = ConvertDecimals(tokenPriceFeed.getPriceFeedDecimals(), 18);
|
||||
|
||||
// Formats an 18 decimal point string with a define number of decimals and precision for use in message generation.
|
||||
this.formatDecimalString = createFormatFunction(this.web3, 2, 6, false);
|
||||
|
||||
@@ -321,7 +321,7 @@ describe("uniswapV3Trader.js", function () {
|
||||
mockTime = Number((await web3.eth.getBlock("latest")).timestamp) + 1;
|
||||
await tokenPriceFeed.update();
|
||||
assert.equal(
|
||||
Number(fromWei(tokenPriceFeed.getLastBlockPrice())).toFixed(4),
|
||||
Number(Number(fromWei(tokenPriceFeed.getLastBlockPrice())).toFixed(4)),
|
||||
(await getCurrentPrice(poolAddress, web3)).toNumber()
|
||||
);
|
||||
|
||||
|
||||
@@ -2565,7 +2565,7 @@
|
||||
retry-request "^4.0.0"
|
||||
teeny-request "^6.0.0"
|
||||
|
||||
"@google-cloud/common@^3.1.0", "@google-cloud/common@^3.4.1":
|
||||
"@google-cloud/common@^3.1.0", "@google-cloud/common@^3.4.1", "@google-cloud/common@^3.7.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-3.7.0.tgz#ee3fba75aeaa614978aebf8740380670026592aa"
|
||||
integrity sha512-oFgpKLjH9JTOAyQd3kB36iSuH8wNSpDKb1TywlB6zcsG0xmJFxLutmfPhz03KUxRMNQOZ1K1Gc9BYvJifVnGVA==
|
||||
@@ -2602,6 +2602,13 @@
|
||||
google-gax "^0.23.0"
|
||||
lodash.merge "^4.6.0"
|
||||
|
||||
"@google-cloud/kms@^2.3.1":
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/kms/-/kms-2.4.4.tgz#e4fb95610668b1475f6623a0b69d32bbad2f73eb"
|
||||
integrity sha512-YqWBZu4+57ac14p1PXvFBPU+tppwRBbI2e5vzg5cn/yi0GSIaU/nmkEYPPHSM7Fi7PS7W13AlcMT6RbVafRBoQ==
|
||||
dependencies:
|
||||
google-gax "^2.17.1"
|
||||
|
||||
"@google-cloud/logging-winston@^4.0.4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/logging-winston/-/logging-winston-4.1.0.tgz#659dd5a1024c0f42dcaa652478d206f82cdb6715"
|
||||
@@ -2708,6 +2715,32 @@
|
||||
through2 "^3.0.0"
|
||||
xdg-basedir "^3.0.0"
|
||||
|
||||
"@google-cloud/storage@^5.8.5":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-5.11.1.tgz#1b32833790e77fc447d5254910a190834a709c72"
|
||||
integrity sha512-DGKHBMqE+OsLPei61yTYOOJKFMvkzVViIafJ+vtCgdXqlSfyEaDK3zXMzIqTb+D9DTJvkEFYi2Wc44eBVo1Gqw==
|
||||
dependencies:
|
||||
"@google-cloud/common" "^3.7.0"
|
||||
"@google-cloud/paginator" "^3.0.0"
|
||||
"@google-cloud/promisify" "^2.0.0"
|
||||
arrify "^2.0.0"
|
||||
async-retry "^1.3.1"
|
||||
compressible "^2.0.12"
|
||||
date-and-time "^1.0.0"
|
||||
duplexify "^4.0.0"
|
||||
extend "^3.0.2"
|
||||
gcs-resumable-upload "^3.3.0"
|
||||
get-stream "^6.0.0"
|
||||
hash-stream-validation "^0.2.2"
|
||||
mime "^2.2.0"
|
||||
mime-types "^2.0.8"
|
||||
onetime "^5.1.0"
|
||||
p-limit "^3.0.1"
|
||||
pumpify "^2.0.0"
|
||||
snakeize "^0.1.0"
|
||||
stream-events "^1.0.1"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
"@google-cloud/trace-agent@^4.2.5":
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/trace-agent/-/trace-agent-4.2.5.tgz#443bf19f08aaceb8cf20bd7591f10efbdebf995a"
|
||||
@@ -6361,7 +6394,14 @@
|
||||
"@types/level-errors" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.104":
|
||||
"@types/lodash.uniqby@^4.7.6":
|
||||
version "4.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.uniqby/-/lodash.uniqby-4.7.6.tgz#672827a701403f07904fe37f0721ae92abfa80e8"
|
||||
integrity sha512-9wBhrm1y6asW50Joj6tsySCNUgzK2tCqL7vtKIej0E9RyeBFdcte7fxUosmFuMoOU0eHqOMK76kCCrK99jxHgg==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*", "@types/lodash@^4.14.104":
|
||||
version "4.14.171"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b"
|
||||
integrity sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==
|
||||
@@ -11098,6 +11138,18 @@ configstore@^4.0.0:
|
||||
write-file-atomic "^2.0.0"
|
||||
xdg-basedir "^3.0.0"
|
||||
|
||||
configstore@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
||||
integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
|
||||
dependencies:
|
||||
dot-prop "^5.2.0"
|
||||
graceful-fs "^4.1.2"
|
||||
make-dir "^3.0.0"
|
||||
unique-string "^2.0.0"
|
||||
write-file-atomic "^3.0.0"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
confusing-browser-globals@^1.0.7, confusing-browser-globals@^1.0.9:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59"
|
||||
@@ -11524,6 +11576,11 @@ crypto-random-string@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
||||
|
||||
crypto-random-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
css-blank-pseudo@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
||||
@@ -11943,6 +12000,11 @@ date-and-time@^0.6.3:
|
||||
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.6.3.tgz#2daee52df67c28bd93bce862756ac86b68cf4237"
|
||||
integrity sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==
|
||||
|
||||
date-and-time@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-1.0.1.tgz#4959b7faf1ec5873e59d926d4528b9223a808a57"
|
||||
integrity sha512-7u+uNfnjWkX+YFQfivvW24TjaJG6ahvTrfw1auq7KlC7osuGcZBIWGBvB9UcENjH6JnLVhMqlRripk1dSHjAUA==
|
||||
|
||||
dateformat@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
@@ -12638,6 +12700,11 @@ dotenv@^8.2.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
|
||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||
|
||||
dotenv@^9.0.0:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05"
|
||||
integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==
|
||||
|
||||
dotignore@^0.1.2, dotignore@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905"
|
||||
@@ -15609,6 +15676,19 @@ gcs-resumable-upload@^1.0.0:
|
||||
pumpify "^1.5.1"
|
||||
stream-events "^1.0.4"
|
||||
|
||||
gcs-resumable-upload@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-3.3.0.tgz#d1a866173f9b47e045d4406cafaa658dbb01e624"
|
||||
integrity sha512-MQKWi+9hOSTyg5/SI1NBW4gAjL1wlkoevHefvr1PCBBXH4uKYLsug5qRrcotWKolDPLfWS51cWaHRN0CTtQNZw==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
configstore "^5.0.0"
|
||||
extend "^3.0.2"
|
||||
gaxios "^4.0.0"
|
||||
google-auth-library "^7.0.0"
|
||||
pumpify "^2.0.0"
|
||||
stream-events "^1.0.4"
|
||||
|
||||
genfun@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
|
||||
@@ -15718,6 +15798,11 @@ get-stream@^5.0.0, get-stream@^5.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
get-value@^2.0.3, get-value@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
@@ -16694,7 +16779,7 @@ hash-base@^3.0.0:
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
hash-stream-validation@^0.2.1:
|
||||
hash-stream-validation@^0.2.1, hash-stream-validation@^0.2.2:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512"
|
||||
integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==
|
||||
@@ -23579,7 +23664,7 @@ p-is-promise@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
|
||||
integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
|
||||
|
||||
p-limit@3.1.0, p-limit@^3.0.2:
|
||||
p-limit@3.1.0, p-limit@^3.0.1, p-limit@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
||||
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
|
||||
@@ -25857,7 +25942,7 @@ pumpify@^1.3.3, pumpify@^1.3.5, pumpify@^1.5.1:
|
||||
inherits "^2.0.3"
|
||||
pump "^2.0.0"
|
||||
|
||||
pumpify@^2.0.1:
|
||||
pumpify@^2.0.0, pumpify@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-2.0.1.tgz#abfc7b5a621307c728b551decbbefb51f0e4aa1e"
|
||||
integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==
|
||||
@@ -30349,6 +30434,13 @@ unique-string@^1.0.0:
|
||||
dependencies:
|
||||
crypto-random-string "^1.0.0"
|
||||
|
||||
unique-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
|
||||
integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
|
||||
dependencies:
|
||||
crypto-random-string "^2.0.0"
|
||||
|
||||
universal-cookie@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.4.tgz#06e8b3625bf9af049569ef97109b4bb226ad798d"
|
||||
@@ -31251,7 +31343,7 @@ web3-core@1.4.0:
|
||||
web3-core-requestmanager "1.4.0"
|
||||
web3-utils "1.4.0"
|
||||
|
||||
web3-core@1.5.0:
|
||||
web3-core@1.5.0, web3-core@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.5.0.tgz#46c09283bcfe197df0c543dbe751650cea157a7f"
|
||||
integrity sha512-1o/etaPSK8tFOWTA6df3t9J6ez4epeyzlNmyh/gx8uHasfa16XLKD8//A9T+O/TmvyQAaA4hWAsQcvlRcuaZ8Q==
|
||||
@@ -31454,7 +31546,7 @@ web3-eth-contract@1.4.0:
|
||||
web3-eth-abi "1.4.0"
|
||||
web3-utils "1.4.0"
|
||||
|
||||
web3-eth-contract@1.5.0:
|
||||
web3-eth-contract@1.5.0, web3-eth-contract@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.5.0.tgz#f584a083316424110af95c3ad00c1c3a8a1796d2"
|
||||
integrity sha512-v4laiJRzdcoDwvqaMCzJH1BUosbTVsd01Qp+9v05Q94KycjkdeahPRXX6PEcUNW/ZF8N006iExUweGjajTZnTA==
|
||||
@@ -32895,6 +32987,11 @@ xdg-basedir@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
|
||||
|
||||
xdg-basedir@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
||||
|
||||
xhr-request-promise@^0.1.2:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c"
|
||||
|
||||
Reference in New Issue
Block a user