diff --git a/package.json b/package.json index a307a0059..882d63be5 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,11 @@ "license": "AGPL-3.0", "private": true, "dependencies": { + "@across-protocol/contracts-v2": "^0.0.37", + "@across-protocol/sdk-v2": "^0.0.1", "@defi-wonderland/smock": "^2.0.7", "@uma/contracts-node": "^0.3.1", "@uma/financial-templates-lib": "2.25.0", - "@across-protocol/contracts-v2": "^0.0.36", - "@across-protocol/sdk-v2": "^0.0.1", "hardhat": "^2.9.0" }, "files": [ @@ -52,9 +52,9 @@ "prettier": "^2.3.2", "prettier-plugin-solidity": "^1.0.0-beta.13", "pretty-quick": "^2.0.1", + "sinon": "^9.0.2", "solhint": "^3.3.6", "solidity-coverage": "^0.7.16", - "sinon": "^9.0.2", "ts-node": "^10.1.0", "typechain": "^5.1.2", "typescript": "^4.5.2" diff --git a/src/interfaces/HubPool.ts b/src/interfaces/HubPool.ts index 6c5b1ad3d..ce0fad3e6 100644 --- a/src/interfaces/HubPool.ts +++ b/src/interfaces/HubPool.ts @@ -1,2 +1,22 @@ +import { BigNumber } from "../utils"; // @notice Passed as input to HubPool.proposeRootBundle export type BundleEvaluationBlockNumbers = number[]; + +export interface PoolRebalanceLeaf { + chainId: BigNumber; + groupIndex: BigNumber; + bundleLpFees: BigNumber[]; + netSendAmounts: BigNumber[]; + runningBalances: BigNumber[]; + leafId: BigNumber; + l1Tokens: string[]; +} + +export interface RelayerRefundLeaf { + amountToReturn: BigNumber; + chainId: BigNumber; + refundAmounts: BigNumber[]; + leafId: BigNumber; + l2TokenAddress: string; + refundAddresses: string[]; +} diff --git a/src/interfaces/SpokePool.ts b/src/interfaces/SpokePool.ts index 424dc13cf..c5c380105 100644 --- a/src/interfaces/SpokePool.ts +++ b/src/interfaces/SpokePool.ts @@ -54,6 +54,19 @@ export interface SlowFill { recipient: string; } +// Used in pool by spokePool to execute a slow relay. +export interface RelayData { + depositor: string; + recipient: string; + destinationToken: string; + amount: BigNumber; + realizedLpFeePct: BigNumber; + relayerFeePct: BigNumber; + depositId: string; + originChainId: string; + destinationChainId: string; +} + export interface UnfilledDeposit { deposit: Deposit; unfilledAmount: BigNumber; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 571f46510..ddf1b8f0b 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1 +1,2 @@ export * from "./SpokePool"; +export * from "./HubPool"; diff --git a/src/utils/ContractInstance.ts b/src/utils/ContractUtils.ts similarity index 71% rename from src/utils/ContractInstance.ts rename to src/utils/ContractUtils.ts index 7fd82b807..f7e772163 100644 --- a/src/utils/ContractInstance.ts +++ b/src/utils/ContractUtils.ts @@ -1,12 +1,12 @@ -import { getNetworkName, Contract, Wallet } from "./"; +import { getNetworkName, Contract, Wallet } from "."; import { getContractArtifact } from "@across-protocol/contracts-v2"; -import { PublicNetworks } from "@uma/common"; +import * as typechain from "@across-protocol/contracts-v2"; //TODO: refactor once we've fixed export from contract repo // Return an ethers contract instance for a deployed contract, imported from the Across-protocol contracts repo. export function getDeployedContract(contractName: string, networkId: number, signer: Wallet): Contract { if (contractName === "SpokePool") contractName = castSpokePoolName(networkId); - console.log("contractName", contractName); + const artifact = getContractArtifact(contractName, networkId); if (!artifact) throw new Error(`Could not find artifact for contract ${contractName} on ${networkId}`); return new Contract(artifact.address, artifact.abi, signer); @@ -22,3 +22,9 @@ export function castSpokePoolName(networkId: number): string { if (networkName.includes("-")) networkName = networkName.substring(0, networkName.indexOf("-")); return `${networkName}_SpokePool`; } + +export function getParamType(contractName: string, functionName: string, paramName: string) { + const artifact: any = typechain[`${[contractName]}__factory`]; + const fragment = artifact.abi.find((fragment) => fragment.name === functionName); + return fragment!.inputs.find((input) => input.name === paramName) || ""; +} diff --git a/src/utils/ExecutionUtils.ts b/src/utils/ExecutionUtils.ts index f4af14e1f..13bd07f83 100644 --- a/src/utils/ExecutionUtils.ts +++ b/src/utils/ExecutionUtils.ts @@ -1,5 +1,4 @@ -import winston from "winston"; -import { delay } from "./"; +import { delay, winston } from "./"; export async function processEndPollingLoop(logger: winston.Logger, fileName: String, pollingDelay: number) { if (pollingDelay === 0) { diff --git a/src/utils/MerkleTreeUtils.ts b/src/utils/MerkleTreeUtils.ts new file mode 100644 index 000000000..193078a7b --- /dev/null +++ b/src/utils/MerkleTreeUtils.ts @@ -0,0 +1,38 @@ +import { getParamType, utils } from "."; +import { RelayData, PoolRebalanceLeaf, RelayerRefundLeaf } from "../interfaces"; +import { MerkleTree } from "@across-protocol/contracts-v2"; + +export async function buildSlowRelayTree(relays: RelayData[]) { + const paramType = await getParamType("MerkleLibTest", "verifySlowRelayFulfillment", "slowRelayFulfillment"); + const hashFn = (input: RelayData) => { + return utils.keccak256(utils.defaultAbiCoder.encode([paramType!], [input])); + }; + return new MerkleTree(relays, hashFn); +} + +export async function buildPoolRebalanceLeafTree(poolRebalanceLeaves: PoolRebalanceLeaf[]) { + for (let i = 0; i < poolRebalanceLeaves.length; i++) { + // The 4 provided parallel arrays must be of equal length. + if ( + poolRebalanceLeaves[i].l1Tokens.length !== poolRebalanceLeaves[i].bundleLpFees.length || + poolRebalanceLeaves[i].netSendAmounts.length !== poolRebalanceLeaves[i].runningBalances.length + ) + throw new Error("Provided lef arrays are not of equal length"); + } + + const paramType = await getParamType("MerkleLibTest", "verifyPoolRebalance", "rebalance"); + const hashFn = (input: PoolRebalanceLeaf) => utils.keccak256(utils.defaultAbiCoder.encode([paramType!], [input])); + return new MerkleTree(poolRebalanceLeaves, hashFn); +} + +export async function buildRelayerRefundTree(relayerRefundLeaves: RelayerRefundLeaf[]) { + for (let i = 0; i < relayerRefundLeaves.length; i++) { + // The 2 provided parallel arrays must be of equal length. + if (relayerRefundLeaves[i].refundAddresses.length != relayerRefundLeaves[i].refundAmounts.length) + throw new Error("Provided lef arrays are not of equal length"); + } + + const paramType = await getParamType("MerkleLibTest", "verifyRelayerRefund", "refund"); + const hashFn = (input: RelayerRefundLeaf) => utils.keccak256(utils.defaultAbiCoder.encode([paramType!], [input])); + return new MerkleTree(relayerRefundLeaves, hashFn); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index fade966ea..e9d9efd7f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,9 +11,10 @@ export * from "./EventUtils"; export * from "./ObjectUtils"; export * from "./FormattingUtils"; export * from "./TransactionPropBuilder"; -export * from "./ContractInstance"; +export * from "./ContractUtils"; export * from "./ExecutionUtils"; export * from "./NetworkUtils"; export * from "./TransactionUtils"; +export * from "./MerkleTreeUtils"; export const zeroAddress = "0x0000000000000000000000000000000000000000"; diff --git a/yarn.lock b/yarn.lock index c8febdd1a..bea753627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@across-protocol/contracts-v2@^0.0.36": - version "0.0.36" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-0.0.36.tgz#33af2952168e5def06c85a2f7fef8c9eece6f163" - integrity sha512-67tYwCbad8Fq+oF82cSYv7Jk0dcba8bmxeNrHkaJIKDEnTTO2vNh3528KMP1COppp4DctFKHfpM2mts26DCYzA== +"@across-protocol/contracts-v2@^0.0.37": + version "0.0.37" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-0.0.37.tgz#e2cc6bd572d70af2543350db03a354b51662b26e" + integrity sha512-kyXH851PeL/ryEHyTina3YgEfKTookai0/oE4HBK8HEqygtibvpzseUrwahj5xjvDATFi+2gZy9I6FIxmXp9SQ== dependencies: "@defi-wonderland/smock" "^2.0.7" "@eth-optimism/contracts" "^0.5.11"