diff --git a/.circleci/config.yml b/.circleci/config.yml index 11f0532e59..215971112d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,7 @@ jobs: docker: - image: circleci/node:lts working_directory: ~/protocol + resource_class: large steps: - restore_cache: key: protocol-{{ .Environment.CIRCLE_SHA1 }} diff --git a/packages/common/package.json b/packages/common/package.json index 6991fa5361..990e400621 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -28,7 +28,7 @@ "/dist/**/*" ], "dependencies": { - "@across-protocol/contracts": "^0.1.2", + "@across-protocol/contracts": "^0.1.4", "@eth-optimism/hardhat-ovm": "^0.2.2", "@ethersproject/bignumber": "^5.0.5", "@google-cloud/kms": "^2.3.1", diff --git a/packages/common/src/hardhat/plugins/ExtendedWeb3.ts b/packages/common/src/hardhat/plugins/ExtendedWeb3.ts index a00695958d..a819c3ef1b 100644 --- a/packages/common/src/hardhat/plugins/ExtendedWeb3.ts +++ b/packages/common/src/hardhat/plugins/ExtendedWeb3.ts @@ -96,7 +96,13 @@ extendEnvironment((_hre) => { const hre = _hre as HRE; hre._artifactCache = {}; hre.getContract = (name, artifactOverrides = {}) => { - if (!hre._artifactCache[name]) hre._artifactCache[name] = hre.artifacts.readArtifactSync(name); + if (!hre._artifactCache[name]) { + // Allows for the caller to bypass the hardhat lookup. + // This is a bit of a hack and will not work for library linking unless linkReferences is provided. + if (artifactOverrides.abi && artifactOverrides.bytecode) + hre._artifactCache[name] = { contractName: name, ...artifactOverrides } as Artifact; + else hre._artifactCache[name] = hre.artifacts.readArtifactSync(name); + } const artifact = { ...hre._artifactCache[name], ...artifactOverrides }; const deployed = async () => { diff --git a/packages/common/src/hardhat/tasks/acrossPool.ts b/packages/common/src/hardhat/tasks/acrossPool.ts deleted file mode 100644 index 271f3a17a2..0000000000 --- a/packages/common/src/hardhat/tasks/acrossPool.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { task, types } from "hardhat/config"; -import { CombinedHRE } from "./types"; - -const { ZERO_ADDRESS } = require("@uma/common"); - -task("deploy-across-pool", "Deploys an L1 across pool and whitelists it within the BridgeAdmin") - .addParam("lptokenname", "Name of the LP tokens deployed for the pool", undefined, types.string) - .addParam("lptokensymbol", "Symbol of the LP tokens deployed for the pool", undefined, types.string) - .addParam("l1tokenaddress", "Address of the token on L1", undefined, types.string) - .addParam("lpfeeratepersecond", "Scales the amount of pending fees per second paid to LPs", undefined, types.string) - .addParam("iswethpool", "Set if this is the across weth pool", undefined, types.string) - .setAction(async function (taskArguments, hre_) { - const hre = hre_ as CombinedHRE; - const { deployments, getNamedAccounts, web3 } = hre; - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - const { lptokenname, lptokensymbol, l1tokenaddress, lpfeeratepersecond, iswethpool } = taskArguments; - - const BridgeAdmin = await deployments.get("BridgeAdmin"); - const bridgeAdmin = new web3.eth.Contract(BridgeAdmin.abi, BridgeAdmin.address); - console.log(`Loaded BridgeAdmin @ ${bridgeAdmin.options.address}`); - - const args = [ - lptokenname, // _lpTokenName - lptokensymbol, // _lpTokenSymbol - bridgeAdmin.options.address, // _bridgeAdmin - l1tokenaddress, // _l1Token - lpfeeratepersecond, // _lpFeeRatePerSecond - Boolean(JSON.parse(iswethpool)), // _isWethPool - ZERO_ADDRESS, // _timer - ]; - - console.log(`Deploying bridgePool from ${deployer}`, args); - - const bridgePool = await deploy("BridgePoolProd", { from: deployer, args, log: true }); - - console.log("Bridge pool deployed @ ", bridgePool.address); - }); diff --git a/packages/common/src/hardhat/tasks/artifacts.ts b/packages/common/src/hardhat/tasks/artifacts.ts index 803fe2842f..54ed2fd2b3 100644 --- a/packages/common/src/hardhat/tasks/artifacts.ts +++ b/packages/common/src/hardhat/tasks/artifacts.ts @@ -129,7 +129,10 @@ task("generate-contracts-frontend", "Generate typescipt for the contracts-fronte ); artifacts.forEach(({ contractName, packageName }) => { - if (fs.existsSync(`typechain/${packageName}/ethers/${contractName}.d.ts`)) + if ( + fs.existsSync(`typechain/${packageName}/ethers/${contractName}.d.ts`) || + fs.existsSync(`typechain/${packageName}/ethers/${contractName}.ts`) + ) fs.appendFileSync( out, `export type { ${contractName} as ${contractName}Ethers } from "../typechain/${packageName}/ethers";\n` @@ -146,7 +149,10 @@ task("generate-contracts-frontend", "Generate typescipt for the contracts-fronte // Write Web3 contract types. artifacts.forEach(({ contractName, packageName }) => { - if (fs.existsSync(`typechain/${packageName}/web3/${contractName}.d.ts`)) + if ( + fs.existsSync(`typechain/${packageName}/web3/${contractName}.d.ts`) || + fs.existsSync(`typechain/${packageName}/web3/${contractName}.ts`) + ) fs.appendFileSync( out, `export type { ${normalizeClassName(contractName)} as ${normalizeClassName( @@ -216,7 +222,10 @@ task("generate-contracts-node", "Generate typescipt for the contracts-node packa ); artifacts.forEach(({ contractName, packageName }) => { - if (fs.existsSync(`typechain/${packageName}/ethers/${contractName}.d.ts`)) + if ( + fs.existsSync(`typechain/${packageName}/ethers/${contractName}.d.ts`) || + fs.existsSync(`typechain/${packageName}/ethers/${contractName}.ts`) + ) fs.appendFileSync( out, `export type { ${contractName} as ${contractName}Ethers } from "../typechain/${packageName}/ethers";\n` @@ -233,7 +242,10 @@ task("generate-contracts-node", "Generate typescipt for the contracts-node packa // Write Web3 contract types. artifacts.forEach(({ contractName, packageName }) => { - if (fs.existsSync(`typechain/${packageName}/web3/${contractName}.d.ts`)) + if ( + fs.existsSync(`typechain/${packageName}/web3/${contractName}.d.ts`) || + fs.existsSync(`typechain/${packageName}/web3/${contractName}.ts`) + ) fs.appendFileSync( out, `export type { ${normalizeClassName(contractName)} as ${normalizeClassName( diff --git a/packages/contracts-frontend/package.json b/packages/contracts-frontend/package.json index 0a8c053d3b..f288fbd096 100644 --- a/packages/contracts-frontend/package.json +++ b/packages/contracts-frontend/package.json @@ -3,7 +3,7 @@ "version": "0.2.1", "description": "UMA smart contracts and unit tests", "devDependencies": { - "@across-protocol/contracts": "^0.1.2", + "@across-protocol/contracts": "^0.1.4", "@ethersproject/abi": "^5.4.0", "@ethersproject/abstract-provider": "^5.4.0", "@ethersproject/contracts": "^5.4.0", @@ -35,7 +35,7 @@ "clean": "rm -rf dist generated typechain", "copy-across-types": "mkdir -p typechain/@across-protocol/contracts && rsync -a $(dirname $(node -p 'require.resolve(\"@across-protocol/contracts/package.json\")'))/contract-types/ typechain/@across-protocol/contracts", "generate-ts": "yarn clean && mkdir -p generated typechain/core && cp -R ../core/contract-types/* typechain/core/ && yarn copy-across-types && yarn hardhat generate-contracts-frontend --out ./generated/index.ts", - "build": "yarn generate-ts && yarn tsc && rsync -R ./typechain/**/**/*.d.ts ./dist", + "build": "yarn generate-ts && yarn tsc && rsync -a --include '*/' --include '*.d.ts' --exclude '*' ./typechain ./dist/", "prepublish": "yarn build" }, "bugs": { diff --git a/packages/contracts-frontend/tsconfig.json b/packages/contracts-frontend/tsconfig.json index ebd4502bd6..a900f35fbd 100644 --- a/packages/contracts-frontend/tsconfig.json +++ b/packages/contracts-frontend/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["generated/index.ts"], + "include": ["generated/index.ts", "typechain/**/*"], "compilerOptions": { "outDir": "./dist", "declaration": true, diff --git a/packages/contracts-node/package.json b/packages/contracts-node/package.json index aed0ea079a..2c39bb33cf 100644 --- a/packages/contracts-node/package.json +++ b/packages/contracts-node/package.json @@ -3,7 +3,7 @@ "version": "0.2.1", "description": "UMA smart contracts and unit tests", "devDependencies": { - "@across-protocol/contracts": "^0.1.2", + "@across-protocol/contracts": "^0.1.4", "@ethersproject/abi": "^5.4.0", "@ethersproject/abstract-provider": "^5.4.0", "@ethersproject/contracts": "^5.4.0", @@ -35,7 +35,7 @@ "clean": "rm -rf dist generated typechain", "copy-across-types": "mkdir -p typechain/@across-protocol/contracts && rsync -a $(dirname $(node -p 'require.resolve(\"@across-protocol/contracts/package.json\")'))/contract-types/ typechain/@across-protocol/contracts", "generate-ts": "yarn clean && mkdir -p generated typechain/core && cp -R ../core/contract-types/* typechain/core/ && yarn copy-across-types && yarn hardhat generate-contracts-node --out ./generated/index.ts", - "build": "yarn generate-ts && yarn tsc && rsync -R ./typechain/**/**/*.d.ts ./dist/packages/contracts-node", + "build": "yarn generate-ts && yarn tsc && rsync -a --include '*/' --include '*.d.ts' --exclude '*' ./typechain ./dist/packages/contracts-node/", "prepublish": "yarn build" }, "bugs": { diff --git a/packages/contracts-node/tsconfig.json b/packages/contracts-node/tsconfig.json index 376240b82a..e08a30cf39 100644 --- a/packages/contracts-node/tsconfig.json +++ b/packages/contracts-node/tsconfig.json @@ -3,7 +3,9 @@ "esModuleInterop": true, "include": [ "generated/index.ts", + "typechain/**/*", "../core/artifacts/**/*.json", + "../../node_modules/@across-protocol/contracts/artifacts/**/*.json", "../../node_modules/@across-protocol/contracts/artifacts/**/*.json" ], "exclude": [ diff --git a/packages/core/contracts/insured-bridge/BridgeAdmin.sol b/packages/core/contracts/insured-bridge/BridgeAdmin.sol deleted file mode 100644 index b3de733496..0000000000 --- a/packages/core/contracts/insured-bridge/BridgeAdmin.sol +++ /dev/null @@ -1,394 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./interfaces//BridgePoolInterface.sol"; -import "./interfaces/BridgeAdminInterface.sol"; -import "./interfaces/MessengerInterface.sol"; -import "../oracle/interfaces/IdentifierWhitelistInterface.sol"; -import "../oracle/interfaces/FinderInterface.sol"; -import "../oracle/implementation/Constants.sol"; -import "../common/interfaces/AddressWhitelistInterface.sol"; -import "../common/implementation/Lockable.sol"; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @notice Administrative contract deployed on L1 that has implicit references to all L2 DepositBoxes. - * @dev This contract is - * responsible for making global variables accessible to BridgePool contracts, which house passive liquidity and - * enable relaying of L2 deposits. - * @dev The owner of this contract can also call permissioned functions on registered L2 DepositBoxes. - */ -contract BridgeAdmin is BridgeAdminInterface, Ownable, Lockable { - // Finder used to point to latest OptimisticOracle and other DVM contracts. - address public override finder; - - // This contract can relay messages to any number of L2 DepositBoxes, one per L2 network, each identified by a - // unique network ID. To relay a message, both the deposit box contract address and a messenger contract address - // need to be stored. The messenger implementation differs for each L2 because L1 --> L2 messaging is non-standard. - // The deposit box contract originate the deposits that can be fulfilled by BridgePool contracts on L1. - mapping(uint256 => DepositUtilityContracts) private _depositContracts; - - // L1 token addresses are mapped to their canonical token address on L2 and the BridgePool contract that houses - // relay liquidity for any deposits of the canonical L2 token. - mapping(address => L1TokenRelationships) private _whitelistedTokens; - - // Set upon construction and can be reset by Owner. - uint32 public override optimisticOracleLiveness; - uint64 public override proposerBondPct; - bytes32 public override identifier; - - // Add this modifier to methods that are expected to bridge messages to a L2 Deposit contract, which - // will cause unexpected behavior if the deposit or messenger helper contract isn't set and valid. - modifier canRelay(uint256 chainId) { - _validateDepositContracts( - _depositContracts[chainId].depositContract, - _depositContracts[chainId].messengerContract - ); - _; - } - - /** - * @notice Construct the Bridge Admin - * @param _finder DVM finder to find other UMA ecosystem contracts. - * @param _optimisticOracleLiveness Timeout that all bridging actions from L2->L1 must wait for a OptimisticOracle response. - * @param _proposerBondPct Percentage of the bridged amount that a relayer must put up as a bond. - * @param _identifier Identifier used when querying the OO for a cross bridge transfer action. - */ - constructor( - address _finder, - uint32 _optimisticOracleLiveness, - uint64 _proposerBondPct, - bytes32 _identifier - ) { - finder = _finder; - require(address(_getCollateralWhitelist()) != address(0), "Invalid finder"); - _setOptimisticOracleLiveness(_optimisticOracleLiveness); - _setProposerBondPct(_proposerBondPct); - _setIdentifier(_identifier); - } - - /************************************** - * ADMIN FUNCTIONS * - **************************************/ - - /** - * @notice Sets a price identifier to use for relayed deposits. BridgePools reads the identifier from this contract. - * @dev Can only be called by the current owner. - * @param _identifier New identifier to set. - */ - function setIdentifier(bytes32 _identifier) public onlyOwner nonReentrant() { - _setIdentifier(_identifier); - } - - /** - * @notice Sets challenge period for relayed deposits. BridgePools will read this value from this contract. - * @dev Can only be called by the current owner. - * @param liveness New OptimisticOracle liveness period to set for relay price requests. - */ - function setOptimisticOracleLiveness(uint32 liveness) public onlyOwner nonReentrant() { - _setOptimisticOracleLiveness(liveness); - } - - /** - * @notice Sets challenge period for relayed deposits. BridgePools will read this value from this contract. - * @dev Can only be called by the current owner. - * @param _proposerBondPct New OptimisticOracle proposer bond % to set for relay price requests. 1e18 = 100%. - */ - function setProposerBondPct(uint64 _proposerBondPct) public onlyOwner nonReentrant() { - _setProposerBondPct(_proposerBondPct); - } - - /** - * @notice Associates the L2 deposit and L1 messenger helper addresses with an L2 network ID. - * @dev Only callable by the current owner. - * @param chainId L2 network ID to set addresses for. - * @param depositContract Address of L2 deposit contract. - * @param messengerContract Address of L1 helper contract that relays messages to L2. - */ - function setDepositContract( - uint256 chainId, - address depositContract, - address messengerContract - ) public onlyOwner nonReentrant() { - _validateDepositContracts(depositContract, messengerContract); - _depositContracts[chainId].depositContract = depositContract; - _depositContracts[chainId].messengerContract = messengerContract; - emit SetDepositContracts(chainId, depositContract, messengerContract); - } - - /** - * @notice Enables the current owner to transfer ownership of a set of owned bridge pools to a new owner. - * @dev Only callable by the current owner. - * @param bridgePools array of bridge pools to transfer ownership. - * @param newAdmin new admin contract to set ownership to. - */ - function transferBridgePoolAdmin(address[] memory bridgePools, address newAdmin) public onlyOwner nonReentrant() { - for (uint8 i = 0; i < bridgePools.length; i++) { - BridgePoolInterface(bridgePools[i]).changeAdmin(newAdmin); - } - emit BridgePoolsAdminTransferred(bridgePools, newAdmin); - } - - /** - * @notice Enable the current owner to change the decay rate at which LP shares accumulate fees for a particular - * BridgePool. The higher this value, the faster LP shares realize pending fees. - * @dev Only callable by the current owner. - * @param bridgePool Bridge Pool to change LP fee rate for. - * @param newLpFeeRate The new rate to set for the `bridgePool`. - */ - function setLpFeeRatePerSecond(address bridgePool, uint64 newLpFeeRate) public onlyOwner nonReentrant() { - BridgePoolInterface(bridgePool).setLpFeeRatePerSecond(newLpFeeRate); - emit SetLpFeeRate(bridgePool, newLpFeeRate); - } - - /************************************************** - * CROSSDOMAIN ADMIN FUNCTIONS * - **************************************************/ - - /** - * @notice Set new contract as the admin address in the L2 Deposit contract. - * @dev Only callable by the current owner. - * @dev msg.value must equal to l1CallValue. - * @param chainId L2 network ID where Deposit contract is deployed. - * @param admin New admin address to set on L2. - * @param l1CallValue Amount of ETH to include in msg.value. Used to pay for L2 fees, but its exact usage varies - * depending on the L2 network that this contract sends a message to. - * @param l2Gas Gas limit to set for relayed message on L2. - * @param l2GasPrice Gas price bid to set for relayed message on L2. - * @param maxSubmissionCost: Arbitrum only: fee deducted from L2 sender's balance to pay for L2 gas. - */ - function setCrossDomainAdmin( - uint256 chainId, - address admin, - uint256 l1CallValue, - uint256 l2Gas, - uint256 l2GasPrice, - uint256 maxSubmissionCost - ) public payable onlyOwner canRelay(chainId) nonReentrant() { - require(admin != address(0), "Admin cannot be zero address"); - _relayMessage( - _depositContracts[chainId].messengerContract, - l1CallValue, - _depositContracts[chainId].depositContract, - msg.sender, - l2Gas, - l2GasPrice, - maxSubmissionCost, - abi.encodeWithSignature("setCrossDomainAdmin(address)", admin) - ); - emit SetCrossDomainAdmin(chainId, admin); - } - - /** - * @notice Sets the minimum time between L2-->L1 token withdrawals in the L2 Deposit contract. - * @dev Only callable by the current owner. - * @dev msg.value must equal to l1CallValue. - * @param chainId L2 network ID where Deposit contract is deployed. - * @param minimumBridgingDelay the new minimum delay. - * @param l1CallValue Amount of ETH to include in msg.value. Used to pay for L2 fees, but its exact usage varies - * depending on the L2 network that this contract sends a message to. - * @param l2Gas Gas limit to set for relayed message on L2. - * @param l2GasPrice Gas price bid to set for relayed message on L2. - * @param maxSubmissionCost: Arbitrum only: fee deducted from L2 sender's balance to pay for L2 gas. - */ - function setMinimumBridgingDelay( - uint256 chainId, - uint64 minimumBridgingDelay, - uint256 l1CallValue, - uint256 l2Gas, - uint256 l2GasPrice, - uint256 maxSubmissionCost - ) public payable onlyOwner canRelay(chainId) nonReentrant() { - _relayMessage( - _depositContracts[chainId].messengerContract, - l1CallValue, - _depositContracts[chainId].depositContract, - msg.sender, - l2Gas, - l2GasPrice, - maxSubmissionCost, - abi.encodeWithSignature("setMinimumBridgingDelay(uint64)", minimumBridgingDelay) - ); - emit SetMinimumBridgingDelay(chainId, minimumBridgingDelay); - } - - /** - * @notice Owner can pause/unpause L2 deposits for a tokens. - * @dev Only callable by Owner of this contract. Will set the same setting in the L2 Deposit contract via the cross - * domain messenger. - * @dev msg.value must equal to l1CallValue. - * @param chainId L2 network ID where Deposit contract is deployed. - * @param l1Token address of L1 Token to enable/disable deposits and relays for. - * @param depositsEnabled bool to set if the deposit box should accept/reject deposits. - * @param l1CallValue Amount of ETH to include in msg.value. Used to pay for L2 fees, but its exact usage varies - * depending on the L2 network that this contract sends a message to. - * @param l2Gas Gas limit to set for relayed message on L2. - * @param l2GasPrice Gas price bid to set for relayed message on L2. - * @param maxSubmissionCost: Arbitrum only: fee deducted from L2 sender's balance to pay for L2 gas. - */ - function setEnableDepositsAndRelays( - uint256 chainId, - address l1Token, - bool depositsEnabled, - uint256 l1CallValue, - uint256 l2Gas, - uint256 l2GasPrice, - uint256 maxSubmissionCost - ) public payable onlyOwner canRelay(chainId) nonReentrant() { - // Disable relays on the BridgePool. - BridgePoolInterface(_whitelistedTokens[l1Token].bridgePool).setRelaysEnabled(depositsEnabled); - - // Send cross-chain message to the associated bridgeDepositBox to disable deposits. - address l2Token = _whitelistedTokens[l1Token].l2Tokens[chainId]; - _relayMessage( - _depositContracts[chainId].messengerContract, - l1CallValue, - _depositContracts[chainId].depositContract, - msg.sender, - l2Gas, - l2GasPrice, - maxSubmissionCost, - abi.encodeWithSignature("setEnableDeposits(address,bool)", l2Token, depositsEnabled) - ); - emit DepositsEnabled(chainId, l2Token, depositsEnabled); - } - - /** - * @notice Privileged account can associate a whitelisted token with its linked token address on L2. The linked L2 - * token can thereafter be deposited into the Deposit contract on L2 and relayed via the BridgePool contract. - * @dev msg.value must equal to l1CallValue. - * @dev This method is also used to to update the address of the bridgePool within a BridgeDepositBox through the - * re-whitelisting of a previously whitelisted token to update the address of the bridge pool in the deposit box. - * @dev Only callable by Owner of this contract. Also initiates a cross-chain call to the L2 Deposit contract to - * whitelist the token mapping. - * @param chainId L2 network ID where Deposit contract is deployed. - * @param l1Token Address of L1 token that can be used to relay L2 token deposits. - * @param l2Token Address of L2 token whose deposits are fulfilled by `l1Token`. - * @param bridgePool Address of BridgePool which manages liquidity to fulfill L2-->L1 relays. - * @param l1CallValue Amount of ETH to include in msg.value. Used to pay for L2 fees, but its exact usage varies - * depending on the L2 network that this contract sends a message to. - * @param l2Gas Gas limit to set for relayed message on L2. - * @param l2GasPrice Gas price bid to set for relayed message on L2. - * @param maxSubmissionCost: Arbitrum only: fee deducted from L2 sender's balance to pay for L2 gas. - */ - function whitelistToken( - uint256 chainId, - address l1Token, - address l2Token, - address bridgePool, - uint256 l1CallValue, - uint256 l2Gas, - uint256 l2GasPrice, - uint256 maxSubmissionCost - ) public payable onlyOwner canRelay(chainId) nonReentrant() { - require(bridgePool != address(0), "BridgePool cannot be zero address"); - require(l2Token != address(0), "L2 token cannot be zero address"); - require(_getCollateralWhitelist().isOnWhitelist(address(l1Token)), "L1Token token not whitelisted"); - - require(address(BridgePoolInterface(bridgePool).l1Token()) == l1Token, "Bridge pool has different L1 token"); - - // Braces to resolve Stack too deep compile error - { - L1TokenRelationships storage l1TokenRelationships = _whitelistedTokens[l1Token]; - l1TokenRelationships.l2Tokens[chainId] = l2Token; // Set the L2Token at the index of the chainId. - l1TokenRelationships.bridgePool = bridgePool; - } - - _relayMessage( - _depositContracts[chainId].messengerContract, - l1CallValue, - _depositContracts[chainId].depositContract, - msg.sender, - l2Gas, - l2GasPrice, - maxSubmissionCost, - abi.encodeWithSignature("whitelistToken(address,address,address)", l1Token, l2Token, bridgePool) - ); - emit WhitelistToken(chainId, l1Token, l2Token, bridgePool); - } - - /************************************** - * VIEW FUNCTIONS * - **************************************/ - function depositContracts(uint256 chainId) external view override returns (DepositUtilityContracts memory) { - return _depositContracts[chainId]; - } - - function whitelistedTokens(address l1Token, uint256 chainId) - external - view - override - returns (address l2Token, address bridgePool) - { - return (_whitelistedTokens[l1Token].l2Tokens[chainId], _whitelistedTokens[l1Token].bridgePool); - } - - /************************************** - * INTERNAL FUNCTIONS * - **************************************/ - - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface) { - return - IdentifierWhitelistInterface( - FinderInterface(finder).getImplementationAddress(OracleInterfaces.IdentifierWhitelist) - ); - } - - function _getCollateralWhitelist() private view returns (AddressWhitelistInterface) { - return - AddressWhitelistInterface( - FinderInterface(finder).getImplementationAddress(OracleInterfaces.CollateralWhitelist) - ); - } - - function _setIdentifier(bytes32 _identifier) private { - require(_getIdentifierWhitelist().isIdentifierSupported(_identifier), "Identifier not registered"); - identifier = _identifier; - emit SetRelayIdentifier(identifier); - } - - function _setOptimisticOracleLiveness(uint32 liveness) private { - // The following constraints are copied from a similar function in the OptimisticOracle contract: - // - https://github.com/UMAprotocol/protocol/blob/dd211c4e3825fe007d1161025a34e9901b26031a/packages/core/contracts/oracle/implementation/OptimisticOracle.sol#L621 - require(liveness < 5200 weeks, "Liveness too large"); - require(liveness > 0, "Liveness cannot be 0"); - optimisticOracleLiveness = liveness; - emit SetOptimisticOracleLiveness(optimisticOracleLiveness); - } - - function _setProposerBondPct(uint64 _proposerBondPct) private { - proposerBondPct = _proposerBondPct; - emit SetProposerBondPct(proposerBondPct); - } - - function _validateDepositContracts(address depositContract, address messengerContract) private pure { - require( - (depositContract != address(0)) && (messengerContract != address(0)), - "Invalid deposit or messenger contract" - ); - } - - // Send msg.value == l1CallValue to Messenger, which can then use it in any way to execute cross domain message. - function _relayMessage( - address messengerContract, - uint256 l1CallValue, - address target, - address user, - uint256 l2Gas, - uint256 l2GasPrice, - uint256 maxSubmissionCost, - bytes memory message - ) private { - require(l1CallValue == msg.value, "Wrong number of ETH sent"); - MessengerInterface(messengerContract).relayMessage{ value: l1CallValue }( - target, - user, - l1CallValue, - l2Gas, - l2GasPrice, - maxSubmissionCost, - message - ); - } -} diff --git a/packages/core/contracts/insured-bridge/BridgeDepositBox.sol b/packages/core/contracts/insured-bridge/BridgeDepositBox.sol deleted file mode 100644 index c501f7ab52..0000000000 --- a/packages/core/contracts/insured-bridge/BridgeDepositBox.sol +++ /dev/null @@ -1,245 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../common/implementation/Testable.sol"; -import "../common/implementation/Lockable.sol"; - -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface TokenLike { - function balanceOf(address guy) external returns (uint256 wad); -} - -interface WETH9Like { - function deposit() external payable; - - function withdraw(uint256 wad) external; -} - -/** - * @title OVM Bridge Deposit Box. - * @notice Accepts deposits on Optimism L2 to relay to Ethereum L1 as part of the UMA insured bridge system. - */ - -abstract contract BridgeDepositBox is Testable, Lockable { - using SafeERC20 for IERC20; - /************************************* - * OVM DEPOSIT BOX DATA STRUCTURES * - *************************************/ - - // ChainID of the L2 this deposit box is deployed on. - uint256 public chainId; - - // Address of WETH on L1. If the deposited token maps to this L1 token then wrap ETH to WETH on the users behalf. - address public l1Weth; - - // Track the total number of deposits. Used as a unique identifier for bridged transfers. - uint256 public numberOfDeposits; - - struct L2TokenRelationships { - address l1Token; - address l1BridgePool; - uint64 lastBridgeTime; - bool depositsEnabled; - } - - // Mapping of whitelisted L2Token to L2TokenRelationships. Contains L1 TokenAddress and the last time this token - // type was bridged. Used to rate limit bridging actions to rate limit withdraws to L1. - mapping(address => L2TokenRelationships) public whitelistedTokens; - - // Minimum time that must elapse between bridging actions for a given token. Used to rate limit bridging back to L1. - uint64 public minimumBridgingDelay; - - /**************************************** - * EVENTS * - ****************************************/ - - event SetMinimumBridgingDelay(uint64 newMinimumBridgingDelay); - event WhitelistToken(address l1Token, address l2Token, uint64 lastBridgeTime, address bridgePool); - event DepositsEnabled(address l2Token, bool depositsEnabled); - event FundsDeposited( - uint256 chainId, - uint256 depositId, - address l1Recipient, - address l2Sender, - address l1Token, - address l2Token, - uint256 amount, - uint64 slowRelayFeePct, - uint64 instantRelayFeePct, - uint64 quoteTimestamp - ); - event TokensBridged(address indexed l2Token, uint256 numberOfTokensBridged, uint256 l1Gas, address indexed caller); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier onlyIfDepositsEnabled(address l2Token) { - require(whitelistedTokens[l2Token].depositsEnabled, "Contract is disabled"); - _; - } - - /** - * @notice Construct the Bridge Deposit Box - * @param _minimumBridgingDelay Minimum seconds that must elapse between L2 -> L1 token transfer to prevent dos. - * @param _chainId Chain identifier for the Bridge deposit box. - * @param _l1Weth Address of Weth on L1. Used to inform if the deposit should wrap ETH to WETH, if deposit is ETH. - * @param timerAddress Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor( - uint64 _minimumBridgingDelay, - uint256 _chainId, - address _l1Weth, - address timerAddress - ) Testable(timerAddress) { - _setMinimumBridgingDelay(_minimumBridgingDelay); - chainId = _chainId; - l1Weth = _l1Weth; - } - - /************************************** - * ADMIN FUNCTIONS * - **************************************/ - - /** - * @notice Changes the minimum time in seconds that must elapse between withdraws from L2 -> L1. - * @param newMinimumBridgingDelay the new minimum delay. - */ - function _setMinimumBridgingDelay(uint64 newMinimumBridgingDelay) internal { - minimumBridgingDelay = newMinimumBridgingDelay; - emit SetMinimumBridgingDelay(minimumBridgingDelay); - } - - /** - * @notice Enables L1 owner to whitelist a L1 Token <-> L2 Token pair for bridging. - * @param l1Token Address of the canonical L1 token. This is the token users will receive on Ethereum. - * @param l2Token Address of the L2 token representation. This is the token users would deposit on optimism. - * @param l1BridgePool Address of the L1 withdrawal pool linked to this L2+L1 token. - */ - function _whitelistToken( - address l1Token, - address l2Token, - address l1BridgePool - ) internal { - whitelistedTokens[l2Token] = L2TokenRelationships({ - l1Token: l1Token, - l1BridgePool: l1BridgePool, - lastBridgeTime: uint64(getCurrentTime()), - depositsEnabled: true - }); - - emit WhitelistToken(l1Token, l2Token, uint64(getCurrentTime()), l1BridgePool); - } - - /** - * @notice L1 owner can enable/disable deposits for a whitelisted token. - * @param l2Token address of L2 token to enable/disable deposits for. - * @param depositsEnabled bool to set if the deposit box should accept/reject deposits. - */ - function _setEnableDeposits(address l2Token, bool depositsEnabled) internal { - whitelistedTokens[l2Token].depositsEnabled = depositsEnabled; - emit DepositsEnabled(l2Token, depositsEnabled); - } - - function bridgeTokens(address l2Token, uint32 l2Gas) public virtual; - - /************************************** - * DEPOSITOR FUNCTIONS * - **************************************/ - - /** - * @notice Called by L2 user to bridge funds between L2 and L1. - * @dev Emits the `FundsDeposited` event which relayers listen for as part of the bridging action. - * @dev The caller must first approve this contract to spend `amount` of `l2Token`. - * @param l1Recipient L1 address that should receive the tokens. - * @param l2Token L2 token to deposit. - * @param amount How many L2 tokens should be deposited. - * @param slowRelayFeePct Max fraction of `amount` that the depositor is willing to pay as a slow relay fee. - * @param instantRelayFeePct Fraction of `amount` that the depositor is willing to pay as an instant relay fee. - * @param quoteTimestamp Timestamp, at which the depositor will be quoted for L1 liquidity. This enables the - * depositor to know the L1 fees before submitting their deposit. Must be within 10 mins of the current time. - */ - function deposit( - address l1Recipient, - address l2Token, - uint256 amount, - uint64 slowRelayFeePct, - uint64 instantRelayFeePct, - uint64 quoteTimestamp - ) public payable onlyIfDepositsEnabled(l2Token) nonReentrant() { - require(isWhitelistToken(l2Token), "deposit token not whitelisted"); - // We limit the sum of slow and instant relay fees to 50% to prevent the user spending all their funds on fees. - // The realizedLPFeePct on L1 is limited to 50% so the total spent on fees does not ever exceed 100%. - require(slowRelayFeePct <= 0.25e18, "slowRelayFeePct must be <= 25%"); - require(instantRelayFeePct <= 0.25e18, "instantRelayFeePct must be <= 25%"); - - // Note that the OVM's notion of `block.timestamp` is different to the main ethereum L1 EVM. The OVM timestamp - // corresponds to the L1 timestamp of the last confirmed L1 ⇒ L2 transaction. The quoteTime must be within 10 - // mins of the current time to allow for this variance. - // Note also that `quoteTimestamp` cannot be less than 10 minutes otherwise the following arithmetic can result - // in underflow. This isn't a problem as the deposit will revert, but the error might be unexpected for clients. - // Consider requiring `quoteTimestamp >= 10 minutes`. - require( - getCurrentTime() >= quoteTimestamp - 10 minutes && getCurrentTime() <= quoteTimestamp + 10 minutes, - "deposit mined after deadline" - ); - // If the address of the L1 token is the l1Weth and there is a msg.value with the transaction then the user - // is sending ETH. In this case, the ETH should be deposited to WETH, which is then bridged to L1. - if (whitelistedTokens[l2Token].l1Token == l1Weth && msg.value > 0) { - require(msg.value == amount, "msg.value must match amount"); - WETH9Like(address(l2Token)).deposit{ value: msg.value }(); - } - // Else, it is a normal ERC20. In this case pull the token from the users wallet as per normal. - // Note: this includes the case where the L2 user has WETH (already wrapped ETH) and wants to bridge them. In - // this case the msg.value will be set to 0, indicating a "normal" ERC20 bridging action. - else IERC20(l2Token).safeTransferFrom(msg.sender, address(this), amount); - - emit FundsDeposited( - chainId, - numberOfDeposits, // depositId: the current number of deposits acts as a deposit ID (nonce). - l1Recipient, - msg.sender, - whitelistedTokens[l2Token].l1Token, - l2Token, - amount, - slowRelayFeePct, - instantRelayFeePct, - quoteTimestamp - ); - - numberOfDeposits += 1; - } - - /************************************** - * VIEW FUNCTIONS * - **************************************/ - - /** - * @notice Checks if a given L2 token is whitelisted. - * @dev Check the whitelisted token's `lastBridgeTime` parameter since its guaranteed to be != 0 once - * the token has been whitelisted. - * @param l2Token L2 token to check against the whitelist. - * @return true if token is whitelised. - */ - function isWhitelistToken(address l2Token) public view returns (bool) { - return whitelistedTokens[l2Token].lastBridgeTime != 0; - } - - function _hasEnoughTimeElapsedToBridge(address l2Token) internal view returns (bool) { - return getCurrentTime() > whitelistedTokens[l2Token].lastBridgeTime + minimumBridgingDelay; - } - - /** - * @notice Designed to be called by implementing contract in `bridgeTokens` method which sends this contract's - * balance of tokens from L2 to L1 via the canonical token bridge. Tokens that can be bridged are whitelisted - * and have had enough time elapsed since the latest bridge (or the time at which at was whitelisted). - * @dev This function is also public for caller convenience. - * @param l2Token L2 token to check bridging status. - * @return true if token is whitelised and enough time has elapsed since the previous bridge. - */ - function canBridge(address l2Token) public view returns (bool) { - return isWhitelistToken(l2Token) && _hasEnoughTimeElapsedToBridge(l2Token); - } -} diff --git a/packages/core/contracts/insured-bridge/BridgePool.sol b/packages/core/contracts/insured-bridge/BridgePool.sol deleted file mode 100644 index e58883695e..0000000000 --- a/packages/core/contracts/insured-bridge/BridgePool.sol +++ /dev/null @@ -1,980 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./interfaces/BridgeAdminInterface.sol"; -import "./interfaces/BridgePoolInterface.sol"; - -import "../oracle/interfaces/SkinnyOptimisticOracleInterface.sol"; -import "../oracle/interfaces/StoreInterface.sol"; -import "../oracle/interfaces/FinderInterface.sol"; -import "../oracle/implementation/Constants.sol"; - -import "../common/implementation/AncillaryData.sol"; -import "../common/implementation/Testable.sol"; -import "../common/implementation/FixedPoint.sol"; -import "../common/implementation/Lockable.sol"; -import "../common/implementation/MultiCaller.sol"; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -interface WETH9Like { - function withdraw(uint256 wad) external; - - function deposit() external payable; -} - -/** - * @notice Contract deployed on L1 that provides methods for "Relayers" to fulfill deposit orders that originated on L2. - * The Relayers can either post capital to fulfill the deposit (instant relay), or request that the funds are taken out - * of a passive liquidity provider pool following a challenge period (slow relay). This contract ingests liquidity from - * passive liquidity providers and returns them claims to withdraw their funds. Liquidity providers are incentivized - * to post collateral by earning a fee per fulfilled deposit order. - * @dev A "Deposit" is an order to send capital from L2 to L1, and a "Relay" is a fulfillment attempt of that order. - */ -contract BridgePool is MultiCaller, Testable, BridgePoolInterface, ERC20, Lockable { - using SafeERC20 for IERC20; - using FixedPoint for FixedPoint.Unsigned; - using Address for address; - - // Token that this contract receives as LP deposits. - IERC20 public override l1Token; - - // Track the total number of relays and uniquely identifies relays. - uint32 public numberOfRelays; - - // Reserves that are unutilized and withdrawable. - uint256 public liquidReserves; - - // Reserves currently utilized due to L2-L1 transactions in flight. - int256 public utilizedReserves; - - // Reserves that are not yet utilized but are pre-allocated for a pending relay. - uint256 public pendingReserves; - - // True If this pool stores WETH. If the withdrawn token is WETH then unwrap and send ETH when finalizing - // relays. Also enable LPs to receive ETH, if they choose, when withdrawing liquidity. - bool public isWethPool; - - // Enables the Bridge Admin to enable/disable relays in this pool. Disables relayDeposit and relayAndSpeedUp. - bool public relaysEnabled = true; - - // Exponential decay exchange rate to accumulate fees to LPs over time. This can be changed via the BridgeAdmin. - uint64 public lpFeeRatePerSecond; - - // Last timestamp that LP fees were updated. - uint32 public lastLpFeeUpdate; - - // Store local instances of contract params to save gas relaying. - uint64 public proposerBondPct; - uint32 public optimisticOracleLiveness; - - // Store local instance of the reserve currency final fee. This is a gas optimization to not re-call the store. - uint256 l1TokenFinalFee; - - // Cumulative undistributed LP fees. As fees accumulate, they are subtracted from this number. - uint256 public undistributedLpFees; - - // Total bond amount held for pending relays. Bonds are released following a successful relay or after a dispute. - uint256 public bonds; - - // Administrative contract that deployed this contract and also houses all state variables needed to relay deposits. - BridgeAdminInterface public bridgeAdmin; - - // Store local instances of the contract instances to save gas relaying. Can be sync with the Finder at any time via - // the syncUmaEcosystemParams() public function. - StoreInterface public store; - SkinnyOptimisticOracleInterface public optimisticOracle; - - // DVM price request identifier that is resolved based on the validity of a relay attempt. - bytes32 public identifier; - - // A Relay represents an attempt to finalize a cross-chain transfer that originated on an L2 DepositBox contract. - // The flow chart between states is as follows: - // - Begin at Uninitialized. - // - When relayDeposit() is called, a new relay is created with state Pending and mapped to the L2 deposit hash. - // - If the relay is disputed, the RelayData gets deleted and the L2 deposit hash has no relay mapped to it anymore. - // - The above statements enable state to transfer between the Uninitialized and Pending states. - // - When settleRelay() is successfully called, the relay state gets set to Finalized and cannot change from there. - // - It is impossible for a relay to be deleted when in Finalized state (and have its state set to Uninitialized) - // because the only way for settleRelay() to succeed is if the price has resolved on the OptimisticOracle. - // - You cannot dispute an already resolved request on the OptimisticOracle. Moreover, the mapping from - // a relay's ancillary data hash to its deposit hash is deleted after a successful settleRelay() call. - enum RelayState { Uninitialized, Pending, Finalized } - - // Data from L2 deposit transaction. - struct DepositData { - uint256 chainId; - uint64 depositId; - address payable l1Recipient; - address l2Sender; - uint256 amount; - uint64 slowRelayFeePct; - uint64 instantRelayFeePct; - uint32 quoteTimestamp; - } - - // Each L2 Deposit can have one Relay attempt at any one time. A Relay attempt is characterized by its RelayData. - struct RelayData { - RelayState relayState; - address slowRelayer; - uint32 relayId; - uint64 realizedLpFeePct; - uint32 priceRequestTime; - uint256 proposerBond; - uint256 finalFee; - } - - // Associate deposits with pending relay data. When the mapped relay hash is empty, new relay attempts can be made - // for this deposit. The relay data contains information necessary to pay out relayers on successful relay. - // Relay hashes are deleted when they are disputed on the OptimisticOracle. - mapping(bytes32 => bytes32) public relays; - - // Map hash of deposit and realized-relay fee to instant relayers. This mapping is checked at settlement time - // to determine if there was a valid instant relayer. - mapping(bytes32 => address) public instantRelays; - - event LiquidityAdded(uint256 amount, uint256 lpTokensMinted, address indexed liquidityProvider); - event LiquidityRemoved(uint256 amount, uint256 lpTokensBurnt, address indexed liquidityProvider); - event DepositRelayed( - bytes32 indexed depositHash, - DepositData depositData, - RelayData relay, - bytes32 relayAncillaryDataHash - ); - event RelaySpedUp(bytes32 indexed depositHash, address indexed instantRelayer, RelayData relay); - - // Note: the difference between a dispute and a cancellation is that a cancellation happens in the case where - // something changes in the OO between request and dispute that causes calls to it to fail. The most common - // case would be an increase in final fee. However, things like whitelisting can also cause problems. - event RelayDisputed(bytes32 indexed depositHash, bytes32 indexed relayHash, address indexed disputer); - event RelayCanceled(bytes32 indexed depositHash, bytes32 indexed relayHash, address indexed disputer); - event RelaySettled(bytes32 indexed depositHash, address indexed caller, RelayData relay); - event BridgePoolAdminTransferred(address oldAdmin, address newAdmin); - event RelaysEnabledSet(bool newRelaysEnabled); - event LpFeeRateSet(uint64 newLpFeeRatePerSecond); - - modifier onlyBridgeAdmin() { - require(msg.sender == address(bridgeAdmin), "Caller not bridge admin"); - _; - } - - modifier onlyIfRelaysEnabld() { - require(relaysEnabled, "Relays are disabled"); - _; - } - - /** - * @notice Construct the Bridge Pool. - * @param _lpTokenName Name of the LP token to be deployed by this contract. - * @param _lpTokenSymbol Symbol of the LP token to be deployed by this contract. - * @param _bridgeAdmin Admin contract deployed alongside on L1. Stores global variables and has owner control. - * @param _l1Token Address of the L1 token that this bridgePool holds. This is the token LPs deposit and is bridged. - * @param _lpFeeRatePerSecond Interest rate payment that scales the amount of pending fees per second paid to LPs. - * @param _isWethPool Toggles if this is the WETH pool. If it is then can accept ETH and wrap to WETH for the user. - * @param _timer Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor( - string memory _lpTokenName, - string memory _lpTokenSymbol, - address _bridgeAdmin, - address _l1Token, - uint64 _lpFeeRatePerSecond, - bool _isWethPool, - address _timer - ) Testable(_timer) ERC20(_lpTokenName, _lpTokenSymbol) { - require(bytes(_lpTokenName).length != 0 && bytes(_lpTokenSymbol).length != 0, "Bad LP token name or symbol"); - bridgeAdmin = BridgeAdminInterface(_bridgeAdmin); - l1Token = IERC20(_l1Token); - lastLpFeeUpdate = uint32(getCurrentTime()); - lpFeeRatePerSecond = _lpFeeRatePerSecond; - isWethPool = _isWethPool; - - syncUmaEcosystemParams(); // Fetch OptimisticOracle and Store addresses and L1Token finalFee. - syncWithBridgeAdminParams(); // Fetch ProposerBondPct OptimisticOracleLiveness, Identifier from the BridgeAdmin. - - emit LpFeeRateSet(lpFeeRatePerSecond); - } - - /************************************************* - * LIQUIDITY PROVIDER FUNCTIONS * - *************************************************/ - - /** - * @notice Add liquidity to the bridge pool. Pulls l1Token from the caller's wallet. The caller is sent back a - * commensurate number of LP tokens (minted to their address) at the prevailing exchange rate. - * @dev The caller must approve this contract to transfer `l1TokenAmount` amount of l1Token if depositing ERC20. - * @dev The caller can deposit ETH which is auto wrapped to WETH. This can only be done if: a) this is the Weth pool - * and b) the l1TokenAmount matches to the transaction msg.value. - * @dev Reentrancy guard not added to this function because this indirectly calls sync() which is guarded. - * @param l1TokenAmount Number of l1Token to add as liquidity. - */ - function addLiquidity(uint256 l1TokenAmount) public payable nonReentrant() { - // If this is the weth pool and the caller sends msg.value then the msg.value must match the l1TokenAmount. - // Else, msg.value must be set to 0. - require((isWethPool && msg.value == l1TokenAmount) || msg.value == 0, "Bad add liquidity Eth value"); - - // Since `exchangeRateCurrent()` reads this contract's balance and updates contract state using it, - // we must call it first before transferring any tokens to this contract. - uint256 lpTokensToMint = (l1TokenAmount * 1e18) / _exchangeRateCurrent(); - _mint(msg.sender, lpTokensToMint); - liquidReserves += l1TokenAmount; - - if (msg.value > 0 && isWethPool) WETH9Like(address(l1Token)).deposit{ value: msg.value }(); - else l1Token.safeTransferFrom(msg.sender, address(this), l1TokenAmount); - - emit LiquidityAdded(l1TokenAmount, lpTokensToMint, msg.sender); - } - - /** - * @notice Removes liquidity from the bridge pool. Burns lpTokenAmount LP tokens from the caller's wallet. The caller - * is sent back a commensurate number of l1Tokens at the prevailing exchange rate. - * @dev The caller does not need to approve the spending of LP tokens as this method directly uses the burn logic. - * @dev Reentrancy guard not added to this function because this indirectly calls sync() which is guarded. - * @param lpTokenAmount Number of lpTokens to redeem for underlying. - * @param sendEth Enable the liquidity provider to remove liquidity in ETH, if this is the WETH pool. - */ - function removeLiquidity(uint256 lpTokenAmount, bool sendEth) public nonReentrant() { - // Can only send eth on withdrawing liquidity iff this is the WETH pool. - require(!sendEth || isWethPool, "Cant send eth"); - uint256 l1TokensToReturn = (lpTokenAmount * _exchangeRateCurrent()) / 1e18; - - // Check that there is enough liquid reserves to withdraw the requested amount. - require(liquidReserves >= (pendingReserves + l1TokensToReturn), "Utilization too high to remove"); - - _burn(msg.sender, lpTokenAmount); - liquidReserves -= l1TokensToReturn; - - if (sendEth) _unwrapWETHTo(payable(msg.sender), l1TokensToReturn); - else l1Token.safeTransfer(msg.sender, l1TokensToReturn); - - emit LiquidityRemoved(l1TokensToReturn, lpTokenAmount, msg.sender); - } - - /************************************** - * RELAYER FUNCTIONS * - **************************************/ - - /** - * @notice Called by Relayer to execute a slow + fast relay from L2 to L1, fulfilling a corresponding deposit order. - * @dev There can only be one pending relay for a deposit. This method is effectively the relayDeposit and - * speedUpRelay methods concatenated. This could be refactored to just call each method, but there - * are some gas savings in combining the transfers and hash computations. - * @dev Caller must have approved this contract to spend the total bond + amount - fees for `l1Token`. - * @dev This function can only be called if relays are enabled for this bridge pool. - * @param depositData the deposit data struct containing all the user's deposit information. - * @param realizedLpFeePct LP fee calculated off-chain considering the L1 pool liquidity at deposit time, before - * quoteTimestamp. The OO acts to verify the correctness of this realized fee. Cannot exceed 50%. - */ - function relayAndSpeedUp(DepositData memory depositData, uint64 realizedLpFeePct) - public - onlyIfRelaysEnabld() - nonReentrant() - { - // If no pending relay for this deposit, then associate the caller's relay attempt with it. - uint32 priceRequestTime = uint32(getCurrentTime()); - - // The realizedLPFeePct should never be greater than 0.5e18 and the slow and instant relay fees should never be - // more than 0.25e18 each. Therefore, the sum of all fee types can never exceed 1e18 (or 100%). - require( - depositData.slowRelayFeePct <= 0.25e18 && - depositData.instantRelayFeePct <= 0.25e18 && - realizedLpFeePct <= 0.5e18, - "Invalid fees" - ); - - // Check if there is a pending relay for this deposit. - bytes32 depositHash = _getDepositHash(depositData); - - // Note: A disputed relay deletes the stored relay hash and enables this require statement to pass. - require(relays[depositHash] == bytes32(0), "Pending relay exists"); - - uint256 proposerBond = _getProposerBond(depositData.amount); - - // Save hash of new relay attempt parameters. - // Note: The liveness for this relay can be changed in the BridgeAdmin, which means that each relay has a - // potentially variable liveness time. This should not provide any exploit opportunities, especially because - // the BridgeAdmin state (including the liveness value) is permissioned to the cross domained owner. - RelayData memory relayData = - RelayData({ - relayState: RelayState.Pending, - slowRelayer: msg.sender, - relayId: numberOfRelays++, // Note: Increment numberOfRelays at the same time as setting relayId to its current value. - realizedLpFeePct: realizedLpFeePct, - priceRequestTime: priceRequestTime, - proposerBond: proposerBond, - finalFee: l1TokenFinalFee - }); - bytes32 relayHash = _getRelayHash(depositData, relayData); - relays[depositHash] = _getRelayDataHash(relayData); - - bytes32 instantRelayHash = _getInstantRelayHash(depositHash, relayData); - require( - // Can only speed up a pending relay without an existing instant relay associated with it. - instantRelays[instantRelayHash] == address(0), - "Relay cannot be sped up" - ); - - // Sanity check that pool has enough balance to cover relay amount + proposer reward. Reward amount will be - // paid on settlement after the OptimisticOracle price request has passed the challenge period. - // Note: liquidReserves should always be <= balance - bonds. - require(liquidReserves - pendingReserves >= depositData.amount, "Insufficient pool balance"); - - // Compute total proposal bond and pull from caller so that the OptimisticOracle can pull it from here. - uint256 totalBond = proposerBond + l1TokenFinalFee; - - // Pull relay amount minus fees from caller and send to the deposit l1Recipient. The total fees paid is the sum - // of the LP fees, the relayer fees and the instant relay fee. - uint256 feesTotal = - _getAmountFromPct( - relayData.realizedLpFeePct + depositData.slowRelayFeePct + depositData.instantRelayFeePct, - depositData.amount - ); - // If the L1 token is WETH then: a) pull WETH from instant relayer b) unwrap WETH c) send ETH to recipient. - uint256 recipientAmount = depositData.amount - feesTotal; - - bonds += totalBond; - pendingReserves += depositData.amount; // Book off maximum liquidity used by this relay in the pending reserves. - - instantRelays[instantRelayHash] = msg.sender; - - l1Token.safeTransferFrom(msg.sender, address(this), recipientAmount + totalBond); - - // If this is a weth pool then unwrap and send eth. - if (isWethPool) { - _unwrapWETHTo(depositData.l1Recipient, recipientAmount); - // Else, this is a normal ERC20 token. Send to recipient. - } else l1Token.safeTransfer(depositData.l1Recipient, recipientAmount); - - emit DepositRelayed(depositHash, depositData, relayData, relayHash); - emit RelaySpedUp(depositHash, msg.sender, relayData); - } - - /** - * @notice Called by Disputer to dispute an ongoing relay. - * @dev The result of this method is to always throw out the relay, providing an opportunity for another relay for - * the same deposit. Between the disputer and proposer, whoever is incorrect loses their bond. Whoever is correct - * gets it back + a payout. - * @dev Caller must have approved this contract to spend the total bond + amount - fees for `l1Token`. - * @param depositData the deposit data struct containing all the user's deposit information. - * @param relayData RelayData logged in the disputed relay. - */ - function disputeRelay(DepositData memory depositData, RelayData memory relayData) public nonReentrant() { - require(relayData.priceRequestTime + optimisticOracleLiveness > getCurrentTime(), "Past liveness"); - require(relayData.relayState == RelayState.Pending, "Not disputable"); - // Validate the input data. - bytes32 depositHash = _getDepositHash(depositData); - _validateRelayDataHash(depositHash, relayData); - - // Submit the proposal and dispute to the OO. - bytes32 relayHash = _getRelayHash(depositData, relayData); - - // Note: in some cases this will fail due to changes in the OO and the method will refund the relayer. - bool success = - _requestProposeDispute( - relayData.slowRelayer, - msg.sender, - relayData.proposerBond, - relayData.finalFee, - _getRelayAncillaryData(relayHash) - ); - - // Drop the relay and remove the bond from the tracked bonds. - bonds -= relayData.finalFee + relayData.proposerBond; - pendingReserves -= depositData.amount; - delete relays[depositHash]; - if (success) emit RelayDisputed(depositHash, _getRelayDataHash(relayData), msg.sender); - else emit RelayCanceled(depositHash, _getRelayDataHash(relayData), msg.sender); - } - - /** - * @notice Called by Relayer to execute a slow relay from L2 to L1, fulfilling a corresponding deposit order. - * @dev There can only be one pending relay for a deposit. - * @dev Caller must have approved this contract to spend the total bond + amount - fees for `l1Token`. - * @dev This function can only be called if relays are enabled for this bridge pool. - * @param depositData the deposit data struct containing all the user's deposit information. - * @param realizedLpFeePct LP fee calculated off-chain considering the L1 pool liquidity at deposit time, before - * quoteTimestamp. The OO acts to verify the correctness of this realized fee. Cannot exceed 50%. - */ - function relayDeposit(DepositData memory depositData, uint64 realizedLpFeePct) - public - onlyIfRelaysEnabld() - nonReentrant() - { - // The realizedLPFeePct should never be greater than 0.5e18 and the slow and instant relay fees should never be - // more than 0.25e18 each. Therefore, the sum of all fee types can never exceed 1e18 (or 100%). - require( - depositData.slowRelayFeePct <= 0.25e18 && - depositData.instantRelayFeePct <= 0.25e18 && - realizedLpFeePct <= 0.5e18, - "Invalid fees" - ); - - // Check if there is a pending relay for this deposit. - bytes32 depositHash = _getDepositHash(depositData); - - // Note: A disputed relay deletes the stored relay hash and enables this require statement to pass. - require(relays[depositHash] == bytes32(0), "Pending relay exists"); - - // If no pending relay for this deposit, then associate the caller's relay attempt with it. - uint32 priceRequestTime = uint32(getCurrentTime()); - - uint256 proposerBond = _getProposerBond(depositData.amount); - - // Save hash of new relay attempt parameters. - // Note: The liveness for this relay can be changed in the BridgeAdmin, which means that each relay has a - // potentially variable liveness time. This should not provide any exploit opportunities, especially because - // the BridgeAdmin state (including the liveness value) is permissioned to the cross domained owner. - RelayData memory relayData = - RelayData({ - relayState: RelayState.Pending, - slowRelayer: msg.sender, - relayId: numberOfRelays++, // Note: Increment numberOfRelays at the same time as setting relayId to its current value. - realizedLpFeePct: realizedLpFeePct, - priceRequestTime: priceRequestTime, - proposerBond: proposerBond, - finalFee: l1TokenFinalFee - }); - relays[depositHash] = _getRelayDataHash(relayData); - - bytes32 relayHash = _getRelayHash(depositData, relayData); - - // Sanity check that pool has enough balance to cover relay amount + proposer reward. Reward amount will be - // paid on settlement after the OptimisticOracle price request has passed the challenge period. - // Note: liquidReserves should always be <= balance - bonds. - require(liquidReserves - pendingReserves >= depositData.amount, "Insufficient pool balance"); - - // Compute total proposal bond and pull from caller so that the OptimisticOracle can pull it from here. - uint256 totalBond = proposerBond + l1TokenFinalFee; - pendingReserves += depositData.amount; // Book off maximum liquidity used by this relay in the pending reserves. - bonds += totalBond; - - l1Token.safeTransferFrom(msg.sender, address(this), totalBond); - emit DepositRelayed(depositHash, depositData, relayData, relayHash); - } - - /** - * @notice Instantly relay a deposit amount minus fees to the l1Recipient. Instant relayer earns a reward following - * the pending relay challenge period. - * @dev We assume that the caller has performed an off-chain check that the deposit data they are attempting to - * relay is valid. If the deposit data is invalid, then the instant relayer has no recourse to receive their funds - * back after the invalid deposit data is disputed. Moreover, no one will be able to resubmit a relay for the - * invalid deposit data because they know it will get disputed again. On the other hand, if the deposit data is - * valid, then even if it is falsely disputed, the instant relayer will eventually get reimbursed because someone - * else will be incentivized to resubmit the relay to earn slow relayer rewards. Once the valid relay is finalized, - * the instant relayer will be reimbursed. Therefore, the caller has the same responsibility as the disputer in - * validating the relay data. - * @dev Caller must have approved this contract to spend the deposit amount of L1 tokens to relay. There can only - * be one instant relayer per relay attempt. You cannot speed up a relay that is past liveness. - * @param depositData Unique set of L2 deposit data that caller is trying to instantly relay. - * @param relayData Parameters of Relay that caller is attempting to speedup. Must hash to the stored relay hash - * for this deposit or this method will revert. - */ - function speedUpRelay(DepositData memory depositData, RelayData memory relayData) public nonReentrant() { - bytes32 depositHash = _getDepositHash(depositData); - _validateRelayDataHash(depositHash, relayData); - bytes32 instantRelayHash = _getInstantRelayHash(depositHash, relayData); - require( - // Can only speed up a pending relay without an existing instant relay associated with it. - getCurrentTime() < relayData.priceRequestTime + optimisticOracleLiveness && - relayData.relayState == RelayState.Pending && - instantRelays[instantRelayHash] == address(0), - "Relay cannot be sped up" - ); - instantRelays[instantRelayHash] = msg.sender; - - // Pull relay amount minus fees from caller and send to the deposit l1Recipient. The total fees paid is the sum - // of the LP fees, the relayer fees and the instant relay fee. - uint256 feesTotal = - _getAmountFromPct( - relayData.realizedLpFeePct + depositData.slowRelayFeePct + depositData.instantRelayFeePct, - depositData.amount - ); - // If the L1 token is WETH then: a) pull WETH from instant relayer b) unwrap WETH c) send ETH to recipient. - uint256 recipientAmount = depositData.amount - feesTotal; - if (isWethPool) { - l1Token.safeTransferFrom(msg.sender, address(this), recipientAmount); - _unwrapWETHTo(depositData.l1Recipient, recipientAmount); - // Else, this is a normal ERC20 token. Send to recipient. - } else l1Token.safeTransferFrom(msg.sender, depositData.l1Recipient, recipientAmount); - - emit RelaySpedUp(depositHash, msg.sender, relayData); - } - - /** - * @notice Reward relayers if a pending relay price request has a price available on the OptimisticOracle. Mark - * the relay as complete. - * @dev We use the relayData and depositData to compute the ancillary data that the relay price request is uniquely - * associated with on the OptimisticOracle. If the price request passed in does not match the pending relay price - * request, then this will revert. - * @param depositData Unique set of L2 deposit data that caller is trying to settle a relay for. - * @param relayData Parameters of Relay that caller is attempting to settle. Must hash to the stored relay hash - * for this deposit. - */ - function settleRelay(DepositData memory depositData, RelayData memory relayData) public nonReentrant() { - bytes32 depositHash = _getDepositHash(depositData); - _validateRelayDataHash(depositHash, relayData); - require(relayData.relayState == RelayState.Pending, "Already settled"); - uint32 expirationTime = relayData.priceRequestTime + optimisticOracleLiveness; - require(expirationTime <= getCurrentTime(), "Not settleable yet"); - - // Note: this check is to give the relayer a small, but reasonable amount of time to complete the relay before - // before it can be "stolen" by someone else. This is to ensure there is an incentive to settle relays quickly. - require( - msg.sender == relayData.slowRelayer || getCurrentTime() > expirationTime + 15 minutes, - "Not slow relayer" - ); - - // Update the relay state to Finalized. This prevents any re-settling of a relay. - relays[depositHash] = _getRelayDataHash( - RelayData({ - relayState: RelayState.Finalized, - slowRelayer: relayData.slowRelayer, - relayId: relayData.relayId, - realizedLpFeePct: relayData.realizedLpFeePct, - priceRequestTime: relayData.priceRequestTime, - proposerBond: relayData.proposerBond, - finalFee: relayData.finalFee - }) - ); - - // Reward relayers and pay out l1Recipient. - // At this point there are two possible cases: - // - This was a slow relay: In this case, a) pay the slow relayer their reward and b) pay the l1Recipient of the - // amount minus the realized LP fee and the slow Relay fee. The transfer was not sped up so no instant fee. - // - This was an instant relay: In this case, a) pay the slow relayer their reward and b) pay the instant relayer - // the full bridging amount, minus the realized LP fee and minus the slow relay fee. When the instant - // relayer called speedUpRelay they were docked this same amount, minus the instant relayer fee. As a - // result, they are effectively paid what they spent when speeding up the relay + the instantRelayFee. - - uint256 instantRelayerOrRecipientAmount = - depositData.amount - - _getAmountFromPct(relayData.realizedLpFeePct + depositData.slowRelayFeePct, depositData.amount); - - // Refund the instant relayer iff the instant relay params match the approved relay. - bytes32 instantRelayHash = _getInstantRelayHash(depositHash, relayData); - address instantRelayer = instantRelays[instantRelayHash]; - - // If this is the WETH pool and the instant relayer is is address 0x0 (i.e the relay was not sped up) then: - // a) withdraw WETH to ETH and b) send the ETH to the recipient. - if (isWethPool && instantRelayer == address(0)) { - _unwrapWETHTo(depositData.l1Recipient, instantRelayerOrRecipientAmount); - // Else, this is a normal slow relay being finalizes where the contract sends ERC20 to the recipient OR this - // is the finalization of an instant relay where we need to reimburse the instant relayer in WETH. - } else - l1Token.safeTransfer( - instantRelayer != address(0) ? instantRelayer : depositData.l1Recipient, - instantRelayerOrRecipientAmount - ); - - // There is a fee and a bond to pay out. The fee goes to whoever settles. The bond always goes back to the - // slow relayer. - // Note: for gas efficiency, we use an if so we can combine these transfers in the event that they are the same - // address. - uint256 slowRelayerReward = _getAmountFromPct(depositData.slowRelayFeePct, depositData.amount); - uint256 totalBond = relayData.finalFee + relayData.proposerBond; - if (relayData.slowRelayer == msg.sender) - l1Token.safeTransfer(relayData.slowRelayer, slowRelayerReward + totalBond); - else { - l1Token.safeTransfer(relayData.slowRelayer, totalBond); - l1Token.safeTransfer(msg.sender, slowRelayerReward); - } - - uint256 totalReservesSent = instantRelayerOrRecipientAmount + slowRelayerReward; - - // Update reserves by amounts changed and allocated LP fees. - pendingReserves -= depositData.amount; - liquidReserves -= totalReservesSent; - utilizedReserves += int256(totalReservesSent); - bonds -= totalBond; - _updateAccumulatedLpFees(); - _allocateLpFees(_getAmountFromPct(relayData.realizedLpFeePct, depositData.amount)); - - emit RelaySettled(depositHash, msg.sender, relayData); - - // Clean up state storage and receive gas refund. This also prevents `priceDisputed()` from being able to reset - // this newly Finalized relay state. - delete instantRelays[instantRelayHash]; - } - - /** - * @notice Synchronize any balance changes in this contract with the utilized & liquid reserves. This would be done - * at the conclusion of an L2 -> L1 token transfer via the canonical token bridge. - */ - function sync() public nonReentrant() { - _sync(); - } - - /** - * @notice Computes the exchange rate between LP tokens and L1Tokens. Used when adding/removing liquidity. - * @return The updated exchange rate between LP tokens and L1 tokens. - */ - function exchangeRateCurrent() public nonReentrant() returns (uint256) { - return _exchangeRateCurrent(); - } - - /** - * @notice Computes the current liquidity utilization ratio. - * @dev Used in computing realizedLpFeePct off-chain. - * @return The current utilization ratio. - */ - function liquidityUtilizationCurrent() public nonReentrant() returns (uint256) { - return _liquidityUtilizationPostRelay(0); - } - - /** - * @notice Computes the liquidity utilization ratio post a relay of known size. - * @dev Used in computing realizedLpFeePct off-chain. - * @param relayedAmount Size of the relayed deposit to factor into the utilization calculation. - * @return The updated utilization ratio accounting for a new `relayedAmount`. - */ - function liquidityUtilizationPostRelay(uint256 relayedAmount) public nonReentrant() returns (uint256) { - return _liquidityUtilizationPostRelay(relayedAmount); - } - - /** - * @notice Return both the current utilization value and liquidity utilization post the relay. - * @dev Used in computing realizedLpFeePct off-chain. - * @param relayedAmount Size of the relayed deposit to factor into the utilization calculation. - * @return utilizationCurrent The current utilization ratio. - * @return utilizationPostRelay The updated utilization ratio accounting for a new `relayedAmount`. - */ - function getLiquidityUtilization(uint256 relayedAmount) - public - nonReentrant() - returns (uint256 utilizationCurrent, uint256 utilizationPostRelay) - { - return (_liquidityUtilizationPostRelay(0), _liquidityUtilizationPostRelay(relayedAmount)); - } - - /** - * @notice Updates the address stored in this contract for the OptimisticOracle and the Store to the latest versions - * set in the the Finder. Also pull finalFee Store these as local variables to make relay methods gas efficient. - * @dev There is no risk of leaving this function public for anyone to call as in all cases we want the addresses - * in this contract to map to the latest version in the Finder and store the latest final fee. - */ - function syncUmaEcosystemParams() public nonReentrant() { - FinderInterface finder = FinderInterface(bridgeAdmin.finder()); - optimisticOracle = SkinnyOptimisticOracleInterface( - finder.getImplementationAddress(OracleInterfaces.SkinnyOptimisticOracle) - ); - - store = StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - l1TokenFinalFee = store.computeFinalFee(address(l1Token)).rawValue; - } - - /** - * @notice Updates the values of stored constants for the proposerBondPct, optimisticOracleLiveness and identifier - * to that set in the bridge Admin. We store these as local variables to make the relay methods more gas efficient. - * @dev There is no risk of leaving this function public for anyone to call as in all cases we want these values - * in this contract to map to the latest version set in the BridgeAdmin. - */ - function syncWithBridgeAdminParams() public nonReentrant() { - proposerBondPct = bridgeAdmin.proposerBondPct(); - optimisticOracleLiveness = bridgeAdmin.optimisticOracleLiveness(); - identifier = bridgeAdmin.identifier(); - } - - /************************************ - * ADMIN FUNCTIONS * - ************************************/ - - /** - * @notice Enable the current bridge admin to transfer admin to to a new address. - * @dev Caller must be BridgeAdmin contract. - * @param _newAdmin Admin address of the new admin. - */ - function changeAdmin(address _newAdmin) public override onlyBridgeAdmin() nonReentrant() { - bridgeAdmin = BridgeAdminInterface(_newAdmin); - emit BridgePoolAdminTransferred(msg.sender, _newAdmin); - } - - /** - * @notice Enable the bridge admin to change the decay rate at which LP shares accumulate fees. The higher this - * value, the faster LP shares realize pending fees. - * @dev Caller must be BridgeAdmin contract. - * @param _newLpFeeRatePerSecond The new rate to set. - */ - function setLpFeeRatePerSecond(uint64 _newLpFeeRatePerSecond) public override onlyBridgeAdmin() nonReentrant() { - lpFeeRatePerSecond = _newLpFeeRatePerSecond; - emit LpFeeRateSet(lpFeeRatePerSecond); - } - - /** - * @notice Enable the bridge admin to enable/disable relays for this pool. Acts as a pause. Only effects - * relayDeposit and relayAndSpeedUp methods. ALl other contract logic remains functional after a pause. - * @dev Caller must be BridgeAdmin contract. - * @param _relaysEnabled The new relaysEnabled state. - */ - function setRelaysEnabled(bool _relaysEnabled) public override onlyBridgeAdmin() nonReentrant() { - relaysEnabled = _relaysEnabled; - emit RelaysEnabledSet(_relaysEnabled); - } - - /************************************ - * VIEW FUNCTIONS * - ************************************/ - - /** - * @notice Computes the current amount of unallocated fees that have accumulated from the previous time this the - * contract was called. - */ - function getAccumulatedFees() public view nonReentrantView() returns (uint256) { - return _getAccumulatedFees(); - } - - /** - * @notice Returns ancillary data containing all relevant Relay data that voters can format into UTF8 and use to - * determine if the relay is valid. - * @dev Helpful method to test that ancillary data is constructed properly. We should consider removing if we don't - * anticipate off-chain bots or users to call this method. - * @param depositData Contains L2 deposit information used by off-chain validators to validate relay. - * @param relayData Contains relay information used by off-chain validators to validate relay. - * @return bytes New ancillary data that can be decoded into UTF8. - */ - function getRelayAncillaryData(DepositData memory depositData, RelayData memory relayData) - public - view - nonReentrantView() - returns (bytes memory) - { - return _getRelayAncillaryData(_getRelayHash(depositData, relayData)); - } - - /************************************** - * INTERNAL & PRIVATE FUNCTIONS * - **************************************/ - - function _liquidityUtilizationPostRelay(uint256 relayedAmount) internal returns (uint256) { - _sync(); // Fetch any balance changes due to token bridging finalization and factor them in. - - // liquidityUtilizationRatio := - // (relayedAmount + pendingReserves + max(utilizedReserves,0)) / (liquidReserves + max(utilizedReserves,0)) - // UtilizedReserves has a dual meaning: if it's greater than zero then it represents funds pending in the bridge - // that will flow from L2 to L1. In this case, we can use it normally in the equation. However, if it is - // negative, then it is already counted in liquidReserves. This occurs if tokens are transferred directly to the - // contract. In this case, ignore it as it is captured in liquid reserves and has no meaning in the numerator. - uint256 flooredUtilizedReserves = utilizedReserves > 0 ? uint256(utilizedReserves) : 0; - uint256 numerator = relayedAmount + pendingReserves + flooredUtilizedReserves; - uint256 denominator = liquidReserves + flooredUtilizedReserves; - - // If the denominator equals zero, return 1e18 (max utilization). - if (denominator == 0) return 1e18; - - // In all other cases, return the utilization ratio. - return (numerator * 1e18) / denominator; - } - - function _sync() internal { - // Check if the l1Token balance of the contract is greater than the liquidReserves. If it is then the bridging - // action from L2 -> L1 has concluded and the local accounting can be updated. - uint256 l1TokenBalance = l1Token.balanceOf(address(this)) - bonds; - if (l1TokenBalance > liquidReserves) { - // utilizedReserves can go to less than zero. This will happen if the accumulated fees exceeds the current - // outstanding utilization. In other words, if outstanding bridging transfers are 0 then utilizedReserves - // will equal the total LP fees accumulated over all time. - utilizedReserves -= int256(l1TokenBalance - liquidReserves); - liquidReserves = l1TokenBalance; - } - } - - function _exchangeRateCurrent() internal returns (uint256) { - if (totalSupply() == 0) return 1e18; // initial rate is 1 pre any mint action. - - // First, update fee counters and local accounting of finalized transfers from L2 -> L1. - _updateAccumulatedLpFees(); // Accumulate all allocated fees from the last time this method was called. - _sync(); // Fetch any balance changes due to token bridging finalization and factor them in. - - // ExchangeRate := (liquidReserves + utilizedReserves - undistributedLpFees) / lpTokenSupply - // Note that utilizedReserves can be negative. If this is the case, then liquidReserves is offset by an equal - // and opposite size. LiquidReserves + utilizedReserves will always be larger than undistributedLpFees so this - // int will always be positive so there is no risk in underflow in type casting in the return line. - int256 numerator = int256(liquidReserves) + utilizedReserves - int256(undistributedLpFees); - return (uint256(numerator) * 1e18) / totalSupply(); - } - - // Return UTF8-decodable ancillary data for relay price request associated with relay hash. - function _getRelayAncillaryData(bytes32 relayHash) private pure returns (bytes memory) { - return AncillaryData.appendKeyValueBytes32("", "relayHash", relayHash); - } - - // Returns hash of unique relay and deposit event. This is added to the relay request's ancillary data. - function _getRelayHash(DepositData memory depositData, RelayData memory relayData) private view returns (bytes32) { - return keccak256(abi.encode(depositData, relayData.relayId, relayData.realizedLpFeePct, address(l1Token))); - } - - // Return hash of relay data, which is stored in state and mapped to a deposit hash. - function _getRelayDataHash(RelayData memory relayData) private pure returns (bytes32) { - return keccak256(abi.encode(relayData)); - } - - // Reverts if the stored relay data hash for `depositHash` does not match `_relayData`. - function _validateRelayDataHash(bytes32 depositHash, RelayData memory relayData) private view { - require( - relays[depositHash] == _getRelayDataHash(relayData), - "Hashed relay params do not match existing relay hash for deposit" - ); - } - - // Return hash of unique instant relay and deposit event. This is stored in state and mapped to a deposit hash. - function _getInstantRelayHash(bytes32 depositHash, RelayData memory relayData) private pure returns (bytes32) { - // Only include parameters that affect the "correctness" of an instant relay. For example, the realized LP fee - // % directly affects how many tokens the instant relayer needs to send to the user, whereas the address of the - // instant relayer does not matter for determining whether an instant relay is "correct". - return keccak256(abi.encode(depositHash, relayData.realizedLpFeePct)); - } - - function _getAccumulatedFees() internal view returns (uint256) { - // UnallocatedLpFees := min(undistributedLpFees*lpFeeRatePerSecond*timeFromLastInteraction,undistributedLpFees) - // The min acts to pay out all fees in the case the equation returns more than the remaining a fees. - uint256 possibleUnpaidFees = - (undistributedLpFees * lpFeeRatePerSecond * (getCurrentTime() - lastLpFeeUpdate)) / (1e18); - return possibleUnpaidFees < undistributedLpFees ? possibleUnpaidFees : undistributedLpFees; - } - - // Update internal fee counters by adding in any accumulated fees from the last time this logic was called. - function _updateAccumulatedLpFees() internal { - // Calculate the unallocatedAccumulatedFees from the last time the contract was called. - uint256 unallocatedAccumulatedFees = _getAccumulatedFees(); - - // Decrement the undistributedLpFees by the amount of accumulated fees. - undistributedLpFees = undistributedLpFees - unallocatedAccumulatedFees; - - lastLpFeeUpdate = uint32(getCurrentTime()); - } - - // Allocate fees to the LPs by incrementing counters. - function _allocateLpFees(uint256 allocatedLpFees) internal { - // Add to the total undistributed LP fees and the utilized reserves. Adding it to the utilized reserves acts to - // track the fees while they are in transit. - undistributedLpFees += allocatedLpFees; - utilizedReserves += int256(allocatedLpFees); - } - - function _getAmountFromPct(uint64 percent, uint256 amount) private pure returns (uint256) { - return (percent * amount) / 1e18; - } - - function _getProposerBond(uint256 amount) private view returns (uint256) { - return _getAmountFromPct(proposerBondPct, amount); - } - - function _getDepositHash(DepositData memory depositData) private view returns (bytes32) { - return keccak256(abi.encode(depositData, address(l1Token))); - } - - // Proposes new price of True for relay event associated with `customAncillaryData` to optimistic oracle. If anyone - // disagrees with the relay parameters and whether they map to an L2 deposit, they can dispute with the oracle. - function _requestProposeDispute( - address proposer, - address disputer, - uint256 proposerBond, - uint256 finalFee, - bytes memory customAncillaryData - ) private returns (bool) { - uint256 totalBond = finalFee + proposerBond; - l1Token.safeApprove(address(optimisticOracle), totalBond); - try - optimisticOracle.requestAndProposePriceFor( - identifier, - uint32(getCurrentTime()), - customAncillaryData, - IERC20(l1Token), - // Set reward to 0, since we'll settle proposer reward payouts directly from this contract after a relay - // proposal has passed the challenge period. - 0, - // Set the Optimistic oracle proposer bond for the price request. - proposerBond, - // Set the Optimistic oracle liveness for the price request. - optimisticOracleLiveness, - proposer, - // Canonical value representing "True"; i.e. the proposed relay is valid. - int256(1e18) - ) - returns (uint256 bondSpent) { - if (bondSpent < totalBond) { - // If the OO pulls less (due to a change in final fee), refund the proposer. - uint256 refund = totalBond - bondSpent; - l1Token.safeTransfer(proposer, refund); - l1Token.safeApprove(address(optimisticOracle), 0); - totalBond = bondSpent; - } - } catch { - // If there's an error in the OO, this means something has changed to make this request undisputable. - // To ensure the request does not go through by default, refund the proposer and return early, allowing - // the calling method to delete the request, but with no additional recourse by the OO. - l1Token.safeTransfer(proposer, totalBond); - l1Token.safeApprove(address(optimisticOracle), 0); - - // Return early noting that the attempt at a proposal + dispute did not succeed. - return false; - } - - SkinnyOptimisticOracleInterface.Request memory request = - SkinnyOptimisticOracleInterface.Request({ - proposer: proposer, - disputer: address(0), - currency: IERC20(l1Token), - settled: false, - proposedPrice: int256(1e18), - resolvedPrice: 0, - expirationTime: getCurrentTime() + optimisticOracleLiveness, - reward: 0, - finalFee: totalBond - proposerBond, - bond: proposerBond, - customLiveness: uint256(optimisticOracleLiveness) - }); - - // Note: don't pull funds until here to avoid any transfers that aren't needed. - l1Token.safeTransferFrom(msg.sender, address(this), totalBond); - l1Token.safeApprove(address(optimisticOracle), totalBond); - // Dispute the request that we just sent. - optimisticOracle.disputePriceFor( - identifier, - uint32(getCurrentTime()), - customAncillaryData, - request, - disputer, - address(this) - ); - - // Return true to denote that the proposal + dispute calls succeeded. - return true; - } - - // Unwraps ETH and does a transfer to a recipient address. If the recipient is a smart contract then sends WETH. - function _unwrapWETHTo(address payable to, uint256 amount) internal { - if (address(to).isContract()) { - l1Token.safeTransfer(to, amount); - } else { - WETH9Like(address(l1Token)).withdraw(amount); - to.transfer(amount); - } - } - - // Added to enable the BridgePool to receive ETH. used when unwrapping Weth. - receive() external payable {} -} - -/** - * @notice This is the BridgePool contract that should be deployed on live networks. It is exactly the same as the - * regular BridgePool contract, but it overrides getCurrentTime to make the call a simply return block.timestamp with - * no branching or storage queries. This is done to save gas. - */ -contract BridgePoolProd is BridgePool { - constructor( - string memory _lpTokenName, - string memory _lpTokenSymbol, - address _bridgeAdmin, - address _l1Token, - uint64 _lpFeeRatePerSecond, - bool _isWethPool, - address _timer - ) BridgePool(_lpTokenName, _lpTokenSymbol, _bridgeAdmin, _l1Token, _lpFeeRatePerSecond, _isWethPool, _timer) {} - - function getCurrentTime() public view virtual override returns (uint256) { - return block.timestamp; - } -} diff --git a/packages/core/contracts/insured-bridge/RateModelStore.sol b/packages/core/contracts/insured-bridge/RateModelStore.sol deleted file mode 100644 index b3a5e83179..0000000000 --- a/packages/core/contracts/insured-bridge/RateModelStore.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "../common/implementation/MultiCaller.sol"; - -/** - * @title Maps rate model objects to L1 token. - * @dev This contract is designed to be queried by off-chain relayers that need to compute realized LP fee %'s before - * submitting relay transactions to a BridgePool contract. Therefore, this contract does not perform any validation on - * the shape of the rate model, which is stored as a string to enable arbitrary data encoding via a stringified JSON - * object. This leaves this contract unopionated on the parameters within the rate model, enabling governance to adjust - * the structure in the future. - */ -contract RateModelStore is Ownable, MultiCaller { - mapping(address => string) public l1TokenRateModels; - - event UpdatedRateModel(address indexed l1Token, string rateModel); - - /** - * @notice Updates rate model string for L1 token. - * @param l1Token the l1 token rate model to update. - * @param rateModel the updated rate model. - */ - function updateRateModel(address l1Token, string memory rateModel) external onlyOwner { - l1TokenRateModels[l1Token] = rateModel; - emit UpdatedRateModel(l1Token, rateModel); - } -} diff --git a/packages/core/contracts/insured-bridge/avm/AVM_BridgeDepositBox.sol b/packages/core/contracts/insured-bridge/avm/AVM_BridgeDepositBox.sol deleted file mode 100644 index 6510adf8f1..0000000000 --- a/packages/core/contracts/insured-bridge/avm/AVM_BridgeDepositBox.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../BridgeDepositBox.sol"; -import "../../external/avm/AVM_CrossDomainEnabled.sol"; - -interface StandardBridgeLike { - function outboundTransfer( - address _l1Token, - address _to, - uint256 _amount, - bytes calldata _data - ) external payable returns (bytes memory); -} - -/** - * @notice AVM specific bridge deposit box. - * @dev Uses AVM cross-domain-enabled logic for access control. - */ - -contract AVM_BridgeDepositBox is BridgeDepositBox, AVM_CrossDomainEnabled { - // Address of the L1 contract that acts as the owner of this Bridge deposit box. - address public crossDomainAdmin; - - // Address of the Arbitrum L2 token gateway. - address public l2GatewayRouter; - - event SetXDomainAdmin(address indexed newAdmin); - - /** - * @notice Construct the Arbitrum Bridge Deposit Box - * @param _l2GatewayRouter Address of the Arbitrum L2 token gateway router for sending tokens from L2->L1. - * @param _crossDomainAdmin Address of the L1 contract that can call admin functions on this contract from L1. - * @param _minimumBridgingDelay Minimum second that must elapse between L2->L1 token transfer to prevent dos. - * @param _chainId L2 Chain identifier this deposit box is deployed on. - * @param _l1Weth Address of Weth on L1. Used to inform if the deposit should wrap ETH to WETH, if deposit is ETH. - * @param timerAddress Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor( - address _l2GatewayRouter, - address _crossDomainAdmin, - uint64 _minimumBridgingDelay, - uint256 _chainId, - address _l1Weth, - address timerAddress - ) BridgeDepositBox(_minimumBridgingDelay, _chainId, _l1Weth, timerAddress) { - l2GatewayRouter = _l2GatewayRouter; - _setCrossDomainAdmin(_crossDomainAdmin); - } - - /************************************** - * ADMIN FUNCTIONS * - **************************************/ - - /** - * @notice Changes the L1 contract that can trigger admin functions on this L2 deposit deposit box. - * @dev This should be set to the address of the L1 contract that ultimately relays a cross-domain message, which - * is expected to be the Arbitrum_Messenger. - * @dev Only callable by the existing crossDomainAdmin via the Arbitrum cross domain messenger. - * @param newCrossDomainAdmin address of the new L1 admin contract. - */ - function setCrossDomainAdmin(address newCrossDomainAdmin) public onlyFromCrossDomainAccount(crossDomainAdmin) { - _setCrossDomainAdmin(newCrossDomainAdmin); - } - - /** - * @notice Changes the minimum time in seconds that must elapse between withdraws from L2->L1. - * @dev Only callable by the existing crossDomainAdmin via the Arbitrum cross domain messenger. - * @param newMinimumBridgingDelay the new minimum delay. - */ - function setMinimumBridgingDelay(uint64 newMinimumBridgingDelay) - public - onlyFromCrossDomainAccount(crossDomainAdmin) - { - _setMinimumBridgingDelay(newMinimumBridgingDelay); - } - - /** - * @notice Enables L1 owner to whitelist a L1 Token <-> L2 Token pair for bridging. - * @dev Only callable by the existing crossDomainAdmin via the Arbitrum cross domain messenger. - * @param l1Token Address of the canonical L1 token. This is the token users will receive on Ethereum. - * @param l2Token Address of the L2 token representation. This is the token users would deposit on Arbitrum. - * @param l1BridgePool Address of the L1 withdrawal pool linked to this L2+L1 token. - */ - function whitelistToken( - address l1Token, - address l2Token, - address l1BridgePool - ) public onlyFromCrossDomainAccount(crossDomainAdmin) { - _whitelistToken(l1Token, l2Token, l1BridgePool); - } - - /** - * @notice L1 owner can enable/disable deposits for a whitelisted token. - * @dev Only callable by the existing crossDomainAdmin via the Arbitrum cross domain messenger. - * @param l2Token address of L2 token to enable/disable deposits for. - * @param depositsEnabled bool to set if the deposit box should accept/reject deposits. - */ - function setEnableDeposits(address l2Token, bool depositsEnabled) - public - onlyFromCrossDomainAccount(crossDomainAdmin) - { - _setEnableDeposits(l2Token, depositsEnabled); - } - - /************************************** - * RELAYER FUNCTIONS * - **************************************/ - - /** - * @notice Called by relayer (or any other EOA) to move a batch of funds from the deposit box, through the canonical - * token bridge, to the L1 Withdraw box. - * @dev The frequency that this function can be called is rate limited by the `minimumBridgingDelay` to prevent spam - * on L1 as the finalization of a L2->L1 tx is quite expensive. - * @param l2Token L2 token to relay over the canonical bridge. - * @param l1Gas Unused by Arbitrum, but included for potential forward compatibility considerations. - */ - function bridgeTokens(address l2Token, uint32 l1Gas) public override nonReentrant() { - uint256 bridgeDepositBoxBalance = TokenLike(l2Token).balanceOf(address(this)); - require(bridgeDepositBoxBalance > 0, "can't bridge zero tokens"); - require(canBridge(l2Token), "non-whitelisted token or last bridge too recent"); - - whitelistedTokens[l2Token].lastBridgeTime = uint64(getCurrentTime()); - - StandardBridgeLike(l2GatewayRouter).outboundTransfer( - whitelistedTokens[l2Token].l1Token, // _l1Token. Address of the L1 token to bridge over. - whitelistedTokens[l2Token].l1BridgePool, // _to. Withdraw, over the bridge, to the l1 withdraw contract. - bridgeDepositBoxBalance, // _amount. Send the full balance of the deposit box to bridge. - "" // _data. We don't need to send any data for the bridging action. - ); - - emit TokensBridged(l2Token, bridgeDepositBoxBalance, l1Gas, msg.sender); - } - - /************************************** - * INTERNAL FUNCTIONS * - **************************************/ - - function _setCrossDomainAdmin(address newCrossDomainAdmin) internal { - require(newCrossDomainAdmin != address(0), "Empty address"); - crossDomainAdmin = newCrossDomainAdmin; - emit SetXDomainAdmin(crossDomainAdmin); - } -} diff --git a/packages/core/contracts/insured-bridge/interfaces/BridgeAdminInterface.sol b/packages/core/contracts/insured-bridge/interfaces/BridgeAdminInterface.sol deleted file mode 100644 index 4142937d25..0000000000 --- a/packages/core/contracts/insured-bridge/interfaces/BridgeAdminInterface.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @notice Helper view methods designed to be called by BridgePool contracts. - */ -interface BridgeAdminInterface { - event SetDepositContracts( - uint256 indexed chainId, - address indexed l2DepositContract, - address indexed l2MessengerContract - ); - event SetCrossDomainAdmin(uint256 indexed chainId, address indexed newAdmin); - event SetRelayIdentifier(bytes32 indexed identifier); - event SetOptimisticOracleLiveness(uint32 indexed liveness); - event SetProposerBondPct(uint64 indexed proposerBondPct); - event WhitelistToken(uint256 chainId, address indexed l1Token, address indexed l2Token, address indexed bridgePool); - event SetMinimumBridgingDelay(uint256 indexed chainId, uint64 newMinimumBridgingDelay); - event DepositsEnabled(uint256 indexed chainId, address indexed l2Token, bool depositsEnabled); - event BridgePoolsAdminTransferred(address[] bridgePools, address indexed newAdmin); - event SetLpFeeRate(address indexed bridgePool, uint64 newLpFeeRatePerSecond); - - function finder() external view returns (address); - - struct DepositUtilityContracts { - address depositContract; // L2 deposit contract where cross-chain relays originate. - address messengerContract; // L1 helper contract that can send a message to the L2 with the mapped network ID. - } - - function depositContracts(uint256) external view returns (DepositUtilityContracts memory); - - struct L1TokenRelationships { - mapping(uint256 => address) l2Tokens; // L2 Chain Id to l2Token address. - address bridgePool; - } - - function whitelistedTokens(address, uint256) external view returns (address l2Token, address bridgePool); - - function optimisticOracleLiveness() external view returns (uint32); - - function proposerBondPct() external view returns (uint64); - - function identifier() external view returns (bytes32); -} diff --git a/packages/core/contracts/insured-bridge/interfaces/BridgePoolInterface.sol b/packages/core/contracts/insured-bridge/interfaces/BridgePoolInterface.sol deleted file mode 100644 index da92068e03..0000000000 --- a/packages/core/contracts/insured-bridge/interfaces/BridgePoolInterface.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface BridgePoolInterface { - function l1Token() external view returns (IERC20); - - function changeAdmin(address newAdmin) external; - - function setLpFeeRatePerSecond(uint64 _newLpFeeRatePerSecond) external; - - function setRelaysEnabled(bool _relaysEnabled) external; -} diff --git a/packages/core/contracts/insured-bridge/ovm/OVM_BridgeDepositBox.sol b/packages/core/contracts/insured-bridge/ovm/OVM_BridgeDepositBox.sol deleted file mode 100644 index d1b6439db9..0000000000 --- a/packages/core/contracts/insured-bridge/ovm/OVM_BridgeDepositBox.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../BridgeDepositBox.sol"; -import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; -import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; -import "@eth-optimism/contracts/L2/messaging/IL2ERC20Bridge.sol"; - -/** - * @notice OVM specific bridge deposit box. - * @dev Uses OVM cross-domain-enabled logic for access control. - */ - -contract OVM_BridgeDepositBox is BridgeDepositBox, CrossDomainEnabled { - // Address of the L1 contract that acts as the owner of this Bridge deposit box. - address public crossDomainAdmin; - - event SetXDomainAdmin(address indexed newAdmin); - - /** - * @notice Construct the Optimism Bridge Deposit Box - * @param _crossDomainAdmin Address of the L1 contract that can call admin functions on this contract from L1. - * @param _minimumBridgingDelay Minimum second that must elapse between L2->L1 token transfer to prevent dos. - * @param _chainId L2 Chain identifier this deposit box is deployed on. - * @param _l1Weth Address of Weth on L1. Used to inform if the deposit should wrap ETH to WETH, if deposit is ETH. - * @param timerAddress Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor( - address _crossDomainAdmin, - uint64 _minimumBridgingDelay, - uint256 _chainId, - address _l1Weth, - address timerAddress - ) - CrossDomainEnabled(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER) - BridgeDepositBox(_minimumBridgingDelay, _chainId, _l1Weth, timerAddress) - { - _setCrossDomainAdmin(_crossDomainAdmin); - } - - /************************************** - * ADMIN FUNCTIONS * - **************************************/ - - /** - * @notice Changes the L1 contract that can trigger admin functions on this L2 deposit deposit box. - * @dev This should be set to the address of the L1 contract that ultimately relays a cross-domain message, which - * is expected to be the Optimism_Messenger. - * @dev Only callable by the existing admin via the Optimism cross domain messenger. - * @param newCrossDomainAdmin address of the new L1 admin contract. - */ - function setCrossDomainAdmin(address newCrossDomainAdmin) public onlyFromCrossDomainAccount(crossDomainAdmin) { - _setCrossDomainAdmin(newCrossDomainAdmin); - } - - /** - * @notice Changes the minimum time in seconds that must elapse between withdraws from L2->L1. - * @dev Only callable by the existing crossDomainAdmin via the optimism cross domain messenger. - * @param newMinimumBridgingDelay the new minimum delay. - */ - function setMinimumBridgingDelay(uint64 newMinimumBridgingDelay) - public - onlyFromCrossDomainAccount(crossDomainAdmin) - { - _setMinimumBridgingDelay(newMinimumBridgingDelay); - } - - /** - * @notice Enables L1 owner to whitelist a L1 Token <-> L2 Token pair for bridging. - * @dev Only callable by the existing crossDomainAdmin via the optimism cross domain messenger. - * @param l1Token Address of the canonical L1 token. This is the token users will receive on Ethereum. - * @param l2Token Address of the L2 token representation. This is the token users would deposit on optimism. - * @param l1BridgePool Address of the L1 withdrawal pool linked to this L2+L1 token. - */ - function whitelistToken( - address l1Token, - address l2Token, - address l1BridgePool - ) public onlyFromCrossDomainAccount(crossDomainAdmin) { - _whitelistToken(l1Token, l2Token, l1BridgePool); - } - - /** - * @notice L1 owner can enable/disable deposits for a whitelisted token. - * @dev Only callable by the existing crossDomainAdmin via the optimism cross domain messenger. - * @param l2Token address of L2 token to enable/disable deposits for. - * @param depositsEnabled bool to set if the deposit box should accept/reject deposits. - */ - function setEnableDeposits(address l2Token, bool depositsEnabled) - public - onlyFromCrossDomainAccount(crossDomainAdmin) - { - _setEnableDeposits(l2Token, depositsEnabled); - } - - /************************************** - * RELAYER FUNCTIONS * - **************************************/ - - /** - * @notice Called by relayer (or any other EOA) to move a batch of funds from the deposit box, through the canonical - * token bridge, to the L1 Withdraw box. - * @dev The frequency that this function can be called is rate limited by the `minimumBridgingDelay` to prevent spam - * on L1 as the finalization of a L2->L1 tx is quite expensive. - * @param l2Token L2 token to relay over the canonical bridge. - * @param l1Gas Unused by optimism, but included for potential forward compatibility considerations. - */ - function bridgeTokens(address l2Token, uint32 l1Gas) public virtual override nonReentrant() { - uint256 bridgeDepositBoxBalance = TokenLike(l2Token).balanceOf(address(this)); - require(bridgeDepositBoxBalance > 0, "can't bridge zero tokens"); - require(canBridge(l2Token), "non-whitelisted token or last bridge too recent"); - - whitelistedTokens[l2Token].lastBridgeTime = uint64(getCurrentTime()); - - IL2ERC20Bridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo( - l2Token, // _l2Token. Address of the L2 token to bridge over. - whitelistedTokens[l2Token].l1BridgePool, // _to. Withdraw, over the bridge, to the l1 withdraw contract. - bridgeDepositBoxBalance, // _amount. Send the full balance of the deposit box to bridge. - l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations - "" // _data. We don't need to send any data for the bridging action. - ); - - emit TokensBridged(l2Token, bridgeDepositBoxBalance, l1Gas, msg.sender); - } - - /************************************** - * INTERNAL FUNCTIONS * - **************************************/ - - function _setCrossDomainAdmin(address newCrossDomainAdmin) internal { - require(newCrossDomainAdmin != address(0), "Bad bridge router address"); - crossDomainAdmin = newCrossDomainAdmin; - emit SetXDomainAdmin(crossDomainAdmin); - } -} diff --git a/packages/core/contracts/insured-bridge/ovm/OVM_OETH_BridgeDepositBox.sol b/packages/core/contracts/insured-bridge/ovm/OVM_OETH_BridgeDepositBox.sol deleted file mode 100644 index 1175c2c1b4..0000000000 --- a/packages/core/contracts/insured-bridge/ovm/OVM_OETH_BridgeDepositBox.sol +++ /dev/null @@ -1,89 +0,0 @@ -pragma solidity ^0.8.0; - -import "./OVM_BridgeDepositBox.sol"; - -/** - * @title OVM_OETH_BridgeDepositBox - * @dev Modified version of OVM_BridgeDepositBox that supports Optimism ETH being sent over the canonical bridge as ETH. - * This is re-wrapped to WETH on L2. All other functionality remains the same. - */ - -contract OVM_OETH_BridgeDepositBox is OVM_BridgeDepositBox { - // l2Eth is ETH on Optimism. This acts as both an ERC20 and ETH on the OVM. In production is deployed at address - // 0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000. We need to know the address in this contract as the Optimism bridge - // does not support WETH so we need to unwrap to ETH first then send over ETH. - address public l2Eth; - // The L1 ETH Wrapper contract receives ETH, wraps it to WETH and sends it to the BridgePool. This enables the - // us to keep the same L1 contracts while supporting Optimsim. - address public l1EthWrapper; - - /** - * @notice Construct the Optimism Bridge Deposit Box - * @param _crossDomainAdmin Address of the L1 contract that can call admin functions on this contract from L1. - * @param _minimumBridgingDelay Minimum second that must elapse between L2->L1 token transfer to prevent dos. - * @param _chainId L2 Chain identifier this deposit box is deployed on. - * @param _l1Weth Address of Weth on L1. Used to inform if a bridging action should wrap ETH to WETH, if the desired - * asset-to-bridge is for a whitelisted token mapped to this L1 Weth token. - * @param _l2Eth Address of ETH on L2. If someone wants to bridge L2 Weth from this contract to L1, then L2 ETH - * should be sent over the Optimism bridge. - * @param _l1EthWrapper Address of custom ETH wrapper on L1. Any ETH sent to this contract will be wrapped to WETH - * and sent to the WETH Bridge Pool. - * @param timerAddress Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor( - address _crossDomainAdmin, - uint64 _minimumBridgingDelay, - uint256 _chainId, - address _l1Weth, - address _l2Eth, - address _l1EthWrapper, - address timerAddress - ) OVM_BridgeDepositBox(_crossDomainAdmin, _minimumBridgingDelay, _chainId, _l1Weth, timerAddress) { - l2Eth = _l2Eth; - l1EthWrapper = _l1EthWrapper; - } - - /** - * @notice Called by relayer (or any other EOA) to move a batch of funds from the deposit box, through the canonical - * token bridge, to the L1 Withdraw box. Implementation is exactly the same as the standard OVM_BridgeDepositBox - * except constructed to work with Optimism ETH by first unwrapping WETH then bridging OETH. The target on L1 is - * not the bridgePool but to the l1EthWrapper that takes any ETH sent to it, wraps it and sends to the BridgePool. - * @dev The frequency that this function can be called is rate limited by the `minimumBridgingDelay` to prevent spam - * on L1 as the finalization of a L2->L1 tx is quite expensive. - * @param l2Token L2 token to relay over the canonical bridge. - * @param l1Gas Unused by optimism, but included for potential forward compatibility considerations. - */ - function bridgeTokens(address l2Token, uint32 l1Gas) public override nonReentrant() { - uint256 bridgeDepositBoxBalance = TokenLike(l2Token).balanceOf(address(this)); - require(bridgeDepositBoxBalance > 0, "can't bridge zero tokens"); - require(canBridge(l2Token), "non-whitelisted token or last bridge too recent"); - - whitelistedTokens[l2Token].lastBridgeTime = uint64(getCurrentTime()); - - address bridgePool = whitelistedTokens[l2Token].l1BridgePool; - - // If the l1Token mapping to the l2Token is l1Weth, then to work with the canonical optimism bridge, we first - // unwrap it to ETH then bridge the newly unwraped L2 ETH over the canonical bridge. On L1 the l1EthWrapper will - // re-wrap the ETH to WETH and send it to the WETH bridge pool. - if (whitelistedTokens[l2Token].l1Token == l1Weth) { - WETH9Like(l2Token).withdraw(bridgeDepositBoxBalance); - l2Token = l2Eth; - bridgePool = l1EthWrapper; - } - IL2ERC20Bridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo( - l2Token, // _l2Token. Address of the L2 token to bridge over. - bridgePool, // _to. Withdraw, over the bridge, to the l1 withdraw contract. - bridgeDepositBoxBalance, // _amount. Send the full balance of the deposit box to bridge. - l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations - "" // _data. We don't need to send any data for the bridging action. - ); - - emit TokensBridged(l2Token, bridgeDepositBoxBalance, l1Gas, msg.sender); - } - - // Fallback function to enable this contract to receive ETH sent to it via WETH unwrapping. When l2ETH is unwrapped - // from l2WETH, the l2ETH is sent to this contract before being sent over the canonical Optimism's bridge. - receive() external payable {} - - fallback() external payable {} -} diff --git a/packages/core/contracts/insured-bridge/ovm/Optimism_Wrapper.sol b/packages/core/contracts/insured-bridge/ovm/Optimism_Wrapper.sol deleted file mode 100644 index 93d874d2ae..0000000000 --- a/packages/core/contracts/insured-bridge/ovm/Optimism_Wrapper.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title Optimism Eth Wrapper - * @dev Any ETH sent to this contract is wrapped into WETH and sent to the set bridge pool. This enables ETH to be sent - * over the canonical Optimism bridge, which does not support WETH bridging. - */ -interface WETH9Like { - function deposit() external payable; - - function transfer(address guy, uint256 wad) external; - - function balanceOf(address guy) external view returns (uint256); -} - -contract Optimism_Wrapper is Ownable { - WETH9Like public weth; - address public bridgePool; - - event ChangedBridgePool(address indexed bridgePool); - - /** - * @notice Construct Optimism Wrapper contract. - * @param _weth l1WethContract address. Normally deployed at 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2. - * @param _bridgePool address of the bridge pool to send Wrapped ETH to when ETH is sent to this contract. - */ - constructor(WETH9Like _weth, address _bridgePool) { - weth = _weth; - bridgePool = _bridgePool; - emit ChangedBridgePool(bridgePool); - } - - /** - * @notice Called by owner of the wrapper to change the destination of the wrapped ETH (bridgePool). - * @param newBridgePool address of the bridge pool to send Wrapped ETH to when ETH is sent to this contract. - */ - function changeBridgePool(address newBridgePool) public onlyOwner { - bridgePool = newBridgePool; - emit ChangedBridgePool(bridgePool); - } - - /** - * @notice Publicly callable function that takes all ETH in this contract, wraps it to WETH and sends it to the - * bridge pool contract. Function is called by fallback functions to automatically wrap ETH to WETH and send at the - * conclusion of a canonical ETH bridging action. - */ - function wrapAndTransfer() public payable { - weth.deposit{ value: address(this).balance }(); - weth.transfer(bridgePool, weth.balanceOf(address(this))); - } - - // Fallback function enable this contract to receive funds when they are unwrapped from the weth contract. - fallback() external payable { - wrapAndTransfer(); - } -} diff --git a/packages/core/contracts/insured-bridge/test/BridgeDepositBoxMock.sol b/packages/core/contracts/insured-bridge/test/BridgeDepositBoxMock.sol deleted file mode 100644 index 1696d89311..0000000000 --- a/packages/core/contracts/insured-bridge/test/BridgeDepositBoxMock.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../BridgeDepositBox.sol"; - -contract BridgeDepositBoxMock is BridgeDepositBox { - using SafeERC20 for IERC20; - - // Address of the L1 contract that acts as the owner of this Bridge deposit box. - address public bridgeAdmin; - - event SetBridgeAdmin(address newBridgeAdmin); - - modifier onlyBridgeAdmin() { - require(msg.sender == bridgeAdmin, "Not bridge admin"); - _; - } - - /** - * @notice Ownable bridge deposit box. Used for testing environments that don't have specific l1/l2 messaging logic. - */ - - constructor( - address _bridgeAdmin, - uint64 _minimumBridgingDelay, - address _l1Weth, - address timerAddress - ) BridgeDepositBox(_minimumBridgingDelay, 10, _l1Weth, timerAddress) { - _setBridgeAdmin(_bridgeAdmin); - } - - /************************************** - * ADMIN FUNCTIONS * - **************************************/ - - /** - * @notice Changes the L1 administrator associated with this L2 deposit deposit box. - * @dev Only callable by the existing bridgeAdmin via the optimism cross domain messenger. - * @param _bridgeAdmin address of the new L1 admin contract. - */ - function setCrossDomainAdmin(address _bridgeAdmin) public onlyBridgeAdmin() { - _setBridgeAdmin(_bridgeAdmin); - } - - /** - * @notice Changes the minimum time in seconds that must elapse between withdraws from L2->L1. - * @dev Only callable by the existing bridgeAdmin via the optimism cross domain messenger. - * @param _minimumBridgingDelay the new minimum delay. - */ - function setMinimumBridgingDelay(uint64 _minimumBridgingDelay) public onlyBridgeAdmin() { - _setMinimumBridgingDelay(_minimumBridgingDelay); - } - - /** - * @notice Enables L1 owner to whitelist a L1 Token <-> L2 Token pair for bridging. - * @dev Only callable by the existing bridgeAdmin via the optimism cross domain messenger. - * @param l1Token Address of the canonical L1 token. This is the token users will receive on Ethereum. - * @param l2Token Address of the L2 token representation. This is the token users would deposit on optimism. - * @param l1BridgePool Address of the L1 withdrawal pool linked to this L2+L1 token. - */ - function whitelistToken( - address l1Token, - address l2Token, - address l1BridgePool - ) public onlyBridgeAdmin() { - _whitelistToken(l1Token, l2Token, l1BridgePool); - } - - /** - * @notice L1 owner can enable/disable deposits for a whitelisted token. - * @dev Only callable by the existing bridgeAdmin via the optimism cross domain messenger. - * @param _l2Token address of L2 token to enable/disable deposits for. - * @param _depositsEnabled bool to set if the deposit box should accept/reject deposits. - */ - function setEnableDeposits(address _l2Token, bool _depositsEnabled) public onlyBridgeAdmin() { - _setEnableDeposits(_l2Token, _depositsEnabled); - } - - /************************************** - * RELAYER FUNCTIONS * - **************************************/ - - /** - * @notice Called by relayer (or any other EOA) to move a batch of funds from the deposit box, through the canonical - * token bridge, to the L1 Withdraw box. - * @dev The frequency that this function can be called is rate limited by the `minimumBridgingDelay` to prevent spam - * on L1 as the finalization of a L2->L1 tx is quite expensive. - * @param l2Token L2 token to relay over the canonical bridge. - * @param l1Gas Unused by optimism, but included for potential forward compatibility considerations. - */ - function bridgeTokens(address l2Token, uint32 l1Gas) public override nonReentrant() { - uint256 bridgeDepositBoxBalance = TokenLike(l2Token).balanceOf(address(this)); - require(bridgeDepositBoxBalance > 0, "can't bridge zero tokens"); - require(canBridge(l2Token), "non-whitelisted token or last bridge too recent"); - - whitelistedTokens[l2Token].lastBridgeTime = uint64(getCurrentTime()); - - // Note in this test contract we simply send the l2 tokens to the l1BridgePool. - IERC20(l2Token).safeTransfer(whitelistedTokens[l2Token].l1BridgePool, bridgeDepositBoxBalance); - - emit TokensBridged(l2Token, bridgeDepositBoxBalance, l1Gas, msg.sender); - } - - /************************************** - * INTERNAL FUNCTIONS * - **************************************/ - - function _setBridgeAdmin(address _l1BridgeAdmin) internal { - require(_l1BridgeAdmin != address(0), "Bad bridge router address"); - bridgeAdmin = _l1BridgeAdmin; - emit SetBridgeAdmin(bridgeAdmin); - } -} diff --git a/packages/core/deploy/036_deploy_across_bridge_admin.js b/packages/core/deploy/036_deploy_across_bridge_admin.js deleted file mode 100644 index 5a5c347a32..0000000000 --- a/packages/core/deploy/036_deploy_across_bridge_admin.js +++ /dev/null @@ -1,23 +0,0 @@ -const func = async function (hre) { - // This migration will fail if contracts the bridge admin depend on are not registered in the Finder. No tests depend - // on this migration so we can continue in this case enabling us to use this in production and skipping in tests. - - const { deployments, getNamedAccounts } = hre; - const { deploy } = deployments; - - const { deployer } = await getNamedAccounts(); - - const Finder = await deployments.get("Finder"); - - const args = [ - Finder.address, // Finder address - 1800, // optimisticOracleLiveness of 30 mins - hre.web3.utils.toWei("0.01"), // 1% of relayed funds must be bonded by the proposer - hre.web3.utils.padRight(hre.web3.utils.utf8ToHex("IS_CROSS_CHAIN_RELAY_VALID"), 64), // price identifier to validate bridging action - ]; - - await deploy("BridgeAdmin", { from: deployer, args, log: true, skipIfAlreadyDeployed: true }); -}; -module.exports = func; -func.tags = ["BridgeAdmin"]; -func.dependencies = ["Finder"]; diff --git a/packages/core/deploy/040_deploy_rate_model_store.js b/packages/core/deploy/036_deploy_rate_model_store.js similarity index 100% rename from packages/core/deploy/040_deploy_rate_model_store.js rename to packages/core/deploy/036_deploy_rate_model_store.js diff --git a/packages/core/deploy/037_deploy_across_optimism_messenger.js b/packages/core/deploy/037_deploy_across_optimism_messenger.js deleted file mode 100644 index f0d9523f86..0000000000 --- a/packages/core/deploy/037_deploy_across_optimism_messenger.js +++ /dev/null @@ -1,23 +0,0 @@ -const func = async function (hre) { - const chainId = await hre.web3.eth.net.getId(); - - const { deployments, getNamedAccounts } = hre; - const { deploy } = deployments; - - const { deployer } = await getNamedAccounts(); - - // Maps chainID to optimism cross-domain messenger contract - const l1ChainIdToMessenger = { - 42: "0x4361d0F75A0186C05f971c566dC6bEa5957483fD", // kovan - 1: "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", // mainnet - }; - - await deploy("Optimism_Messenger", { - from: deployer, - args: [l1ChainIdToMessenger[chainId]], - log: true, - skipIfAlreadyDeployed: true, - }); -}; -module.exports = func; -func.tags = ["Optimism_Messenger"]; diff --git a/packages/core/deploy/041_deploy_arbitrum_parent_messenger.js b/packages/core/deploy/037_deploy_arbitrum_parent_messenger.js similarity index 100% rename from packages/core/deploy/041_deploy_arbitrum_parent_messenger.js rename to packages/core/deploy/037_deploy_arbitrum_parent_messenger.js diff --git a/packages/core/deploy/038_deploy_across_ovm_bridge_deposit_box.js b/packages/core/deploy/038_deploy_across_ovm_bridge_deposit_box.js deleted file mode 100644 index bb027ee5f6..0000000000 --- a/packages/core/deploy/038_deploy_across_ovm_bridge_deposit_box.js +++ /dev/null @@ -1,33 +0,0 @@ -// This deploy script should be run on an optimism provider. -const { getAddress } = require("@uma/contracts-node"); -const { ZERO_ADDRESS } = require("@uma/common"); -const func = async function (hre) { - const chainId = await hre.web3.eth.net.getId(); - - const { deployments, getNamedAccounts } = hre; - const { deploy } = deployments; - - const { deployer } = await getNamedAccounts(); - - // Map L2 chain IDs to L1 chain IDs to find associated bridgeAdmin addresses for a given L2 chain ID. - const l2ChainIdToL1 = { - 69: 42, // optimism testnet -> kovan - 10: 1, // optimism mainnet -> mainnet - }; - - const bridgeAdminAddress = l2ChainIdToL1[chainId] - ? await getAddress("Optimism_Messenger", l2ChainIdToL1[chainId]) - : (await deployments.get("Optimism_Messenger")).address; - - const args = [ - bridgeAdminAddress, - 1800, // minimumBridgingDelay of 30 mins - chainId, - await getAddress("WETH9", l2ChainIdToL1[chainId]), - ZERO_ADDRESS, // timer address - ]; - - await deploy("OVM_BridgeDepositBox", { from: deployer, args, log: true, skipIfAlreadyDeployed: true }); -}; -module.exports = func; -func.tags = ["OVM_BridgeDepositBox"]; diff --git a/packages/core/deploy/042_deploy_arbitrum_child_messenger.js b/packages/core/deploy/038_deploy_arbitrum_child_messenger.js similarity index 100% rename from packages/core/deploy/042_deploy_arbitrum_child_messenger.js rename to packages/core/deploy/038_deploy_arbitrum_child_messenger.js diff --git a/packages/core/deploy/043_deploy_oracle_spoke.js b/packages/core/deploy/039_deploy_oracle_spoke.js similarity index 100% rename from packages/core/deploy/043_deploy_oracle_spoke.js rename to packages/core/deploy/039_deploy_oracle_spoke.js diff --git a/packages/core/deploy/044_deploy_oracle_hub.js b/packages/core/deploy/040_deploy_oracle_hub.js similarity index 100% rename from packages/core/deploy/044_deploy_oracle_hub.js rename to packages/core/deploy/040_deploy_oracle_hub.js diff --git a/packages/core/deploy/045_deploy_governor_spoke.js b/packages/core/deploy/041_deploy_governor_spoke.js similarity index 100% rename from packages/core/deploy/045_deploy_governor_spoke.js rename to packages/core/deploy/041_deploy_governor_spoke.js diff --git a/packages/core/deploy/046_deploy_governor_hub.js b/packages/core/deploy/042_deploy_governor_hub.js similarity index 100% rename from packages/core/deploy/046_deploy_governor_hub.js rename to packages/core/deploy/042_deploy_governor_hub.js diff --git a/packages/core/deploy/047_deploy_boba_parent_messenger.js b/packages/core/deploy/043_deploy_boba_parent_messenger.js similarity index 100% rename from packages/core/deploy/047_deploy_boba_parent_messenger.js rename to packages/core/deploy/043_deploy_boba_parent_messenger.js diff --git a/packages/core/deploy/048_deploy_boba_child_messenger.js b/packages/core/deploy/044_deploy_boba_child_messenger.js similarity index 100% rename from packages/core/deploy/048_deploy_boba_child_messenger.js rename to packages/core/deploy/044_deploy_boba_child_messenger.js diff --git a/packages/core/deploy/049_deploy_optimism_parent_messenger.js b/packages/core/deploy/045_deploy_optimism_parent_messenger.js similarity index 100% rename from packages/core/deploy/049_deploy_optimism_parent_messenger.js rename to packages/core/deploy/045_deploy_optimism_parent_messenger.js diff --git a/packages/core/deploy/050_deploy_optimism_child_messenger.js b/packages/core/deploy/046_deploy_optimism_child_messenger.js similarity index 100% rename from packages/core/deploy/050_deploy_optimism_child_messenger.js rename to packages/core/deploy/046_deploy_optimism_child_messenger.js diff --git a/packages/core/deploy/051_deploy_admin_child_messenger.js b/packages/core/deploy/047_deploy_admin_child_messenger.js similarity index 100% rename from packages/core/deploy/051_deploy_admin_child_messenger.js rename to packages/core/deploy/047_deploy_admin_child_messenger.js diff --git a/packages/core/deploy/051_deploy_proposer.js b/packages/core/deploy/048_deploy_proposer.js similarity index 100% rename from packages/core/deploy/051_deploy_proposer.js rename to packages/core/deploy/048_deploy_proposer.js diff --git a/packages/core/networks/1.json b/packages/core/networks/1.json index 1a605cac19..6fdc6bfa1d 100644 --- a/packages/core/networks/1.json +++ b/packages/core/networks/1.json @@ -132,22 +132,10 @@ "deploymentName": "Boba_Messenger", "address": "0x0cd4998978F6cd06f8DbD227A802018A3c227ACC" }, - { - "contractName": "Optimism_Wrapper", - "address": "0xcffb47dc5bd4f6dc475e98ff92647a583389ee08" - }, - { - "contractName": "BridgeAdmin", - "address": "0x30B44C676A05F1264d1dE9cC31dB5F2A945186b6" - }, { "contractName": "SkinnyOptimisticOracle", "address": "0xeE3Afe347D5C74317041E2618C49534dAf887c24" }, - { - "contractName": "RateModelStore", - "address": "0xd18fFeb5fdd1F2e122251eA7Bf357D8Af0B60B50" - }, { "contractName": "Arbitrum_ParentMessenger", "address": "0x278C6e83876B6D7163a2141B0eB6404a07EBcAB7" @@ -174,31 +162,6 @@ "deploymentName": "Optimism_ParentMessenger", "address": "0x6455D800D1Dbf9B1C3a63c67CcF22B9308728dC4" }, - { - "contractName": "BridgePoolProd", - "deploymentName": "WETH_BridgePool", - "address": "0x7355Efc63Ae731f584380a9838292c7046c1e433" - }, - { - "contractName": "BridgePoolProd", - "deploymentName": "USDC_BridgePool", - "address": "0x256C8919CE1AB0e33974CF6AA9c71561Ef3017b6 " - }, - { - "contractName": "BridgePoolProd", - "deploymentName": "UMA_BridgePool", - "address": "0xdfe0ec39291e3b60ACa122908f86809c9eE64E90" - }, - { - "contractName": "BridgePoolProd", - "deploymentName": "BADGER_BridgePool", - "address": "0x43298F9f91a4545dF64748e78a2c777c580573d6" - }, - { - "contractName": "BridgePoolProd", - "deploymentName": "WBTC_BridgePool", - "address": "0x02fbb64517E1c6ED69a6FAa3ABf37Db0482f1152" - }, { "contractName": "Proposer", "address": "0x226726Ac52e6e948D1B7eA9168F9Ff2E27DbcbB5" diff --git a/packages/core/networks/10.json b/packages/core/networks/10.json index cc97bc6185..ca01295a31 100644 --- a/packages/core/networks/10.json +++ b/packages/core/networks/10.json @@ -1,8 +1,4 @@ [ - { - "contractName": "OVM_OETH_BridgeDepositBox", - "address": "0x3bad7ad0728f9917d1bf08af5782dcbd516cdd96" - }, { "contractName": "Registry", "address": "0xa4199d73ae206d49c966cF16c58436851f87d47F" diff --git a/packages/core/networks/288.json b/packages/core/networks/288.json index a26d14cb31..e87e01e976 100644 --- a/packages/core/networks/288.json +++ b/packages/core/networks/288.json @@ -1,8 +1,4 @@ [ - { - "contractName": "OVM_OETH_BridgeDepositBox", - "address": "0xCD43CEa89DF8fE39031C03c24BC24480e942470B" - }, { "contractName": "Finder", "address": "0xad8fD1f418FB860A383c9D4647880af7f043Ef39" diff --git a/packages/core/networks/4.json b/packages/core/networks/4.json index cd3a1a3889..02bf3cecf9 100644 --- a/packages/core/networks/4.json +++ b/packages/core/networks/4.json @@ -63,10 +63,6 @@ "contractName": "SourceOracle", "address": "0x110009DB52a1AcA1A885F494ff5717017D0A8257" }, - { - "contractName": "BridgeAdmin", - "address": "0x5e01469f733a47CA50e7b4366Ef601fD0FE05Dd1" - }, { "contractName": "Arbitrum_ParentMessenger", "address": "0x0fe9C5A29d79e928B41642EEbB9590dB4667eD70" diff --git a/packages/core/networks/42.json b/packages/core/networks/42.json index 67f927fcba..85401074ab 100644 --- a/packages/core/networks/42.json +++ b/packages/core/networks/42.json @@ -95,10 +95,6 @@ "contractName": "RangeBondLongShortPairFinancialProductLibrary", "address": "0x09381C827438605B5ea3e760A27666Be3497602C" }, - { - "contractName": "BridgeAdmin", - "address": "0xf7241a1CF50540402C8e3e84Ab6F753bd7e6756a" - }, { "contractName": "SkinnyOptimisticOracle", "address": "0xAa04b5D40574Fb8C001249B24d1c6B35a207F0bD" diff --git a/packages/core/networks/42161.json b/packages/core/networks/42161.json index 9ebdb60911..5ec8c50241 100644 --- a/packages/core/networks/42161.json +++ b/packages/core/networks/42161.json @@ -1,8 +1,4 @@ [ - { - "contractName": "AVM_BridgeDepositBox", - "address": "0xd8c6dd978a3768f7ddfe3a9aad2c3fd75fa9b6fd" - }, { "contractName": "Finder", "address": "0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1" diff --git a/packages/core/networks/69.json b/packages/core/networks/69.json deleted file mode 100644 index 597ac8d2c9..0000000000 --- a/packages/core/networks/69.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "contractName": "OVM_BridgeDepositBox", - "address": "0xD8c6dD978a3768F7DDfE3A9aAD2c3Fd75Fa9B6Fd" - } -] diff --git a/packages/core/test/insured-bridge/AVM_BridgeDepositBox.js b/packages/core/test/insured-bridge/AVM_BridgeDepositBox.js deleted file mode 100644 index 9219053090..0000000000 --- a/packages/core/test/insured-bridge/AVM_BridgeDepositBox.js +++ /dev/null @@ -1,228 +0,0 @@ -// This set of tests validates the BridgeDepositBox AVM (Abribtum) specific logic such as L1->L2 function calls and the -// use of the AVM token bridge. Bridge Deposit logic is not directly tested. For this see the BridgeDepositBox.js tests. - -const hre = require("hardhat"); -const { web3 } = hre; -const { toWei } = web3.utils; -const { getContract, assertEventEmitted } = hre; - -const { didContractThrow, ZERO_ADDRESS } = require("@uma/common"); - -const { applyL1ToL2Alias } = require("../helpers/ArbitrumHelper"); -const { deployContractMock } = require("../helpers/SmockitHelper"); - -const { assert } = require("chai"); - -// Tested contract -const BridgeDepositBox = getContract("AVM_BridgeDepositBox"); - -// Fetch the artifacts to create a mock arbitrum gateway router -const { L2GatewayRouter__factory } = require("arb-ts"); - -// Helper contracts -const Token = getContract("ExpandedERC20"); -const Timer = getContract("Timer"); - -// Contract objects -let depositBox, l1TokenAddress, l2Token, timer, l2GatewayRouterMock; - -// As these tests are in the context of l2, we dont have the deployed notion of an "L1 Token". The L1 token is within -// another domain (L1). To represent this, we can generate a random address to represent the L1 token. -l1TokenAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - -const minimumBridgingDelay = 60; // L2->L1 token bridging must wait at least this time. -const depositAmount = toWei("50"); -const slowRelayFeePct = toWei("0.005"); -const instantRelayFeePct = toWei("0.005"); -const quoteTimestampOffset = 60; // 60 seconds into the past. -const chainId = 42161; // Arbitrum mainnet chain ID. - -describe("AVM_BridgeDepositBox", () => { - // Account objects - let accounts, deployer, user1, bridgeAdminCrossDomainAlias, bridgeAdmin, rando, bridgePool; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [deployer, user1, bridgeAdmin, rando, bridgePool] = accounts; - - timer = await Timer.new().send({ from: deployer }); - - // Generate the aliased address of bridgeAdmin. This is the address that'll get sent via the canonical bridge when - // the bridge admin calls methods to L2. See the wallet with some eth. Enable account impersonation in hre. - bridgeAdminCrossDomainAlias = applyL1ToL2Alias(bridgeAdmin); - await web3.eth.sendTransaction({ from: deployer, to: bridgeAdminCrossDomainAlias, value: toWei("1") }); - await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [bridgeAdminCrossDomainAlias] }); - }); - - beforeEach(async function () { - l2Token = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token.methods.addMember(1, deployer).send({ from: deployer }); - - // Mint tokens to user - await l2Token.methods.mint(user1, toWei("100")).send({ from: deployer }); - - // Setup the Arbitrum bridge contracts - l2GatewayRouterMock = await deployContractMock("L2GatewayRouter", {}, L2GatewayRouter__factory); - - depositBox = await BridgeDepositBox.new( - l2GatewayRouterMock.options.address, - bridgeAdmin, - minimumBridgingDelay, - chainId, - ZERO_ADDRESS, // weth address. Weth mode not used in these tests - timer.options.address - ).send({ from: deployer }); - }); - describe("Box admin logic", () => { - // Only the crossDomainAdmin, called via the canonical bridge, can: a) change the L1 withdraw contract, - // b) whitelist collateral or c) disable deposits. In production, the crossDomainAdmin will be the Messenger. - // In these tests mock this as being any crossDomainAdmin, calling via the l2MessengerImpersonator. - it("Change L1 admin contract", async () => { - // Owner should start out as the set owner. - assert.equal(await depositBox.methods.crossDomainAdmin().call(), bridgeAdmin); - - // Trying to transfer ownership from non-cross-domain owner should fail. - assert(await didContractThrow(depositBox.methods.setCrossDomainAdmin(user1).send({ from: rando }))); - - // Calling from the correct cross domain aliased address should work. - const tx = await depositBox.methods.setCrossDomainAdmin(user1).send({ from: bridgeAdminCrossDomainAlias }); - assert.equal(await depositBox.methods.crossDomainAdmin().call(), user1); - await assertEventEmitted(tx, depositBox, "SetXDomainAdmin", (ev) => { - return ev.newAdmin == user1; - }); - }); - - it("Set minimum bridging delay", async () => { - // Bridging delay should be set initially to the correct value. - assert.equal(await depositBox.methods.minimumBridgingDelay().call(), minimumBridgingDelay); - - // Trying to change bridging delay from non-cross-domain owner should fail. - assert(await didContractThrow(depositBox.methods.setMinimumBridgingDelay(55).send({ from: rando }))); - // Calling from the correct cross domain aliased address should work. - const tx = await depositBox.methods.setMinimumBridgingDelay(55).send({ from: bridgeAdminCrossDomainAlias }); - assert.equal(await depositBox.methods.minimumBridgingDelay().call(), 55); - await assertEventEmitted(tx, depositBox, "SetMinimumBridgingDelay", (ev) => { - return ev.newMinimumBridgingDelay == 55; - }); - }); - - it("Whitelist collateral", async () => { - // Trying to whitelist tokens from something other than the l2MessengerImpersonator should fail. - assert( - await didContractThrow( - depositBox.methods.whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool).send({ from: rando }) - ) - ); - - // Calling from the correct cross domain aliased address should work. - const tx = await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: bridgeAdminCrossDomainAlias }); - assert.equal( - (await depositBox.methods.whitelistedTokens(l2Token.options.address).call()).l1Token, - l1TokenAddress - ); - const expectedLastBridgeTime = await timer.methods.getCurrentTime().call(); - await assertEventEmitted(tx, depositBox, "WhitelistToken", (ev) => { - return ( - ev.l1Token == l1TokenAddress && - ev.l2Token == l2Token.options.address && - ev.lastBridgeTime == expectedLastBridgeTime && - ev.bridgePool == bridgePool - ); - }); - }); - - it("Disable deposits", async () => { - // Trying to disable tokens from something other than the l2MessengerImpersonator should fail. - assert( - await didContractThrow( - depositBox.methods.setEnableDeposits(l2Token.options.address, false).send({ from: rando }) - ) - ); - - // Calling from the correct cross domain aliased address should work. - const tx = await depositBox.methods - .setEnableDeposits(l2Token.options.address, false) - .send({ from: bridgeAdminCrossDomainAlias }); - assert.equal((await depositBox.methods.whitelistedTokens(l2Token.options.address).call()).depositsEnabled, false); - await assertEventEmitted(tx, depositBox, "DepositsEnabled", (ev) => { - return ev.l2Token === l2Token.options.address && ev.depositsEnabled == false; - }); - }); - }); - - describe("Box bridging logic", () => { - beforeEach(async function () { - // Whitelist the token in the deposit box and send the tokens to the deposit box. - await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: bridgeAdminCrossDomainAlias }); - }); - it("Can initiate cross-domain bridging action", async () => { - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Advance time enough to enable bridging of this token. - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay + 1) - .send({ from: deployer }); - - const tx = await depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }); - - await assertEventEmitted(tx, depositBox, "TokensBridged", (ev) => { - return ( - ev.l2Token == l2Token.options.address && - ev.numberOfTokensBridged == depositAmount && - ev.l1Gas == 0 && - ev.caller == rando - ); - }); - - // We should be able to check the mock L2 gateway router and see that there was a function call to the - // outboundTransfer method called by the Deposit box for the correct token, amount and l1Recipient. - const tokenBridgingCallsToBridge = - l2GatewayRouterMock.smocked["outboundTransfer(address,address,uint256,bytes)"].calls; - assert.equal(tokenBridgingCallsToBridge.length, 1); // only 1 call - const call = tokenBridgingCallsToBridge[0]; - - assert.equal(call._l1Token, l1TokenAddress); // right token. - assert.equal(call._to, bridgePool); // right recipient. - assert.equal(call._amount.toString(), depositAmount); // right amount. We deposited 50e18. - assert.equal(call._data.toString(), "0x"); // right amount. We deposited 50e18. - }); - it("Reverts if not enough time elapsed", async () => { - // Same as before except dont advance timestamp enough for bridging action. - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Advance time enough to enable bridging of this token. - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay - 10) - .send({ from: deployer }); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts on bridging 0 tokens", async () => { - // Don't do any deposits. balance should be zero and should revert as 0 token bridge action. - assert.equal(await l2Token.methods.balanceOf(depositBox.options.address).call(), "0"); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts if token not whitelisted", async () => { - // Create a new ERC20 and mint them directly to he depositBox.. Bridging should fail as not whitelisted. - const l2Token_nonWhitelisted = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.addMember(1, deployer).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.mint(depositBox.options.address, toWei("100")).send({ from: deployer }); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/ArbitrumMessenger.js b/packages/core/test/insured-bridge/ArbitrumMessenger.js deleted file mode 100644 index ec36412d71..0000000000 --- a/packages/core/test/insured-bridge/ArbitrumMessenger.js +++ /dev/null @@ -1,290 +0,0 @@ -const hre = require("hardhat"); -const { web3, assertEventEmitted } = hre; -const { runDefaultFixture, ZERO_ADDRESS, didContractThrow, interfaceName } = require("@uma/common"); -const { getContract } = hre; -const { utf8ToHex, toWei } = web3.utils; - -const { assert } = require("chai"); - -const { deployContractMock } = require("../helpers/SmockitHelper"); - -// Tested contracts -const Arbitrum_InboxMock = getContract("Arbitrum_InboxMock"); -const Arbitrum_Messenger = getContract("Arbitrum_Messenger"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgePool = getContract("BridgePool"); -const Timer = getContract("Timer"); -const Store = getContract("Store"); -const Finder = getContract("Finder"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); -const IdentifierWhitelist = getContract("IdentifierWhitelist"); -const AddressWhitelist = getContract("AddressWhitelist"); -const ERC20 = getContract("ERC20"); - -// Contract objects -let arbitrumMessenger; -let bridgeAdmin; -let finder; -let store; -let l1InboxMock; -let depositBox; -let identifierWhitelist; -let collateralWhitelist; -let timer; - -// Test function inputs -const defaultGasLimit = 10_000_000; -const defaultGasPrice = toWei("1", "gwei"); -const defaultL1CallValue = 10_000_000_000; -const maxSubmissionCost = 10_000_000_000; -const defaultIdentifier = utf8ToHex("IS_CROSS_CHAIN_RELAY_VALID"); -const defaultLiveness = 7200; -const defaultProposerBondPct = toWei("0.05"); -const lpFeeRatePerSecond = toWei("0.0000015"); -const defaultBridgingDelay = 60; -const chainId = "10"; -let l1Token; -let l2Token; -let bridgePool; - -describe("ArbitrumMessenger integration with BridgeAdmin", () => { - let accounts, owner, rando, rando2, depositBoxImpersonator; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [owner, rando, rando2, depositBoxImpersonator] = accounts; - l2Token = rando2; - await runDefaultFixture(hre); - timer = await Timer.new().send({ from: owner }); - finder = await Finder.new().send({ from: owner }); - l1Token = await ERC20.new("", "").send({ from: owner }); - collateralWhitelist = await AddressWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.CollateralWhitelist), collateralWhitelist.options.address) - .send({ from: owner }); - - identifierWhitelist = await IdentifierWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.IdentifierWhitelist), identifierWhitelist.options.address) - .send({ from: owner }); - - await identifierWhitelist.methods.addSupportedIdentifier(defaultIdentifier).send({ from: owner }); - - // The initialization of the bridge pool requires there to be an address of both the store and the - // SkinnyOptimisticOracle set in the finder. - store = await Store.new({ rawValue: "0" }, { rawValue: "0" }, timer.options.address).send({ from: owner }); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: toWei("1") }).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.Store), store.options.address) - .send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.SkinnyOptimisticOracle), rando) - .send({ from: owner }); - }); - beforeEach(async function () { - l1InboxMock = await deployContractMock("Arbitrum_InboxMock", {}, Arbitrum_InboxMock); - - arbitrumMessenger = await Arbitrum_Messenger.new(l1InboxMock.options.address).send({ from: owner }); - - bridgeAdmin = await BridgeAdmin.new( - finder.options.address, - defaultLiveness, - defaultProposerBondPct, - defaultIdentifier - ).send({ from: owner }); - - bridgePool = await BridgePool.new( - "LP Token", - "LPT", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, // not set to weth pool - timer.options.address - ).send({ from: owner }); - - // Configuring the deposit box doesn't affect this test since we only check the smocked contract ABI data. But in - // production the cross domain admin of the deposit box should be the Messenger contract, which is the msg.sender - // of the cross chain message. - depositBox = await BridgeDepositBox.new( - arbitrumMessenger.options.address, - defaultBridgingDelay, - ZERO_ADDRESS, // weth address. Weth mode not used in these tests - ZERO_ADDRESS // timer address - ).send({ from: owner }); - }); - it("relayMessage basic checks", async function () { - const relayMessageTxn = arbitrumMessenger.methods.relayMessage( - depositBox.options.address, - owner, - defaultL1CallValue, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost, - "0x" - ); - - // Only callable by owner - assert(await didContractThrow(relayMessageTxn.send({ from: rando, value: defaultL1CallValue }))); - assert.ok(await relayMessageTxn.send({ from: owner, value: defaultL1CallValue })); - - // Must set msg.value = defaultL1CallValue - assert(await didContractThrow(relayMessageTxn.send({ from: owner, value: 0 }))); - const tx = await relayMessageTxn.send({ from: owner, value: defaultL1CallValue }); - - // Events - await assertEventEmitted(tx, arbitrumMessenger, "RelayedMessage", (ev) => { - return ( - ev.from === owner && - ev.to === depositBox.options.address && - ev.seqNum === "0" && - ev.userToRefund === owner && - ev.l1CallValue === defaultL1CallValue.toString() && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.maxSubmissionCost === maxSubmissionCost.toString() && - ev.data === null - ); - }); - }); - describe("Cross domain Admin functions", () => { - beforeEach(async function () { - await arbitrumMessenger.methods.transferOwnership(bridgeAdmin.options.address).send({ from: owner }); - }); - it("Whitelist tokens", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, arbitrumMessenger.options.address) - .send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - defaultL1CallValue, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner, value: defaultL1CallValue }); - const whitelistCallToMessengerCall = l1InboxMock.smocked.createRetryableTicketNoRefundAliasRewrite.calls[0]; - - // Inbox should receive msg.value - assert.equal((await web3.eth.getBalance(l1InboxMock.options.address)).toString(), defaultL1CallValue); - - // Validate xchain message - assert.equal(whitelistCallToMessengerCall.destAddr, depositBoxImpersonator); - assert.equal(whitelistCallToMessengerCall.l2CallValue, "0"); - assert.equal(whitelistCallToMessengerCall.maxSubmissionCost, maxSubmissionCost); - assert.equal(whitelistCallToMessengerCall.excessFeeRefundAddress, owner); - assert.equal(whitelistCallToMessengerCall.callValueRefundAddress, owner); - assert.equal(whitelistCallToMessengerCall.maxGas, defaultGasLimit); - assert.equal(whitelistCallToMessengerCall.gasPriceBid, defaultGasPrice); - const expectedAbiData = depositBox.methods - .whitelistToken(l1Token.options.address, l2Token, bridgePool.options.address) - .encodeABI(); - assert.equal(whitelistCallToMessengerCall.data, expectedAbiData, "xchain message bytes unexpected"); - }); - it("Set bridge admin", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, arbitrumMessenger.options.address) - .send({ from: owner }); - await bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, defaultL1CallValue, defaultGasLimit, defaultGasPrice, maxSubmissionCost) - .send({ from: owner, value: defaultL1CallValue }); - const setAdminCallToMessengerCall = l1InboxMock.smocked.createRetryableTicketNoRefundAliasRewrite.calls[0]; - - // Inbox should receive msg.value - assert.equal((await web3.eth.getBalance(l1InboxMock.options.address)).toString(), defaultL1CallValue); - - // Validate xchain message - assert.equal(setAdminCallToMessengerCall.destAddr, depositBoxImpersonator); - assert.equal(setAdminCallToMessengerCall.l2CallValue, "0"); - assert.equal(setAdminCallToMessengerCall.maxSubmissionCost, maxSubmissionCost); - assert.equal(setAdminCallToMessengerCall.excessFeeRefundAddress, owner); - assert.equal(setAdminCallToMessengerCall.callValueRefundAddress, owner); - assert.equal(setAdminCallToMessengerCall.maxGas, defaultGasLimit); - assert.equal(setAdminCallToMessengerCall.gasPriceBid, defaultGasPrice); - const expectedAbiData = depositBox.methods.setCrossDomainAdmin(rando).encodeABI(); - assert.equal(setAdminCallToMessengerCall.data, expectedAbiData, "xchain message bytes unexpected"); - }); - it("Set minimum bridge delay", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, arbitrumMessenger.options.address) - .send({ from: owner }); - await bridgeAdmin.methods - .setMinimumBridgingDelay( - chainId, - defaultBridgingDelay, - defaultL1CallValue, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner, value: defaultL1CallValue }); - const setDelayCallToMessengerCall = l1InboxMock.smocked.createRetryableTicketNoRefundAliasRewrite.calls[0]; - - // Inbox should receive msg.value - assert.equal((await web3.eth.getBalance(l1InboxMock.options.address)).toString(), defaultL1CallValue); - - // Validate xchain message - assert.equal(setDelayCallToMessengerCall.destAddr, depositBoxImpersonator); - assert.equal(setDelayCallToMessengerCall.l2CallValue, "0"); - assert.equal(setDelayCallToMessengerCall.maxSubmissionCost, maxSubmissionCost); - assert.equal(setDelayCallToMessengerCall.excessFeeRefundAddress, owner); - assert.equal(setDelayCallToMessengerCall.callValueRefundAddress, owner); - assert.equal(setDelayCallToMessengerCall.maxGas, defaultGasLimit); - assert.equal(setDelayCallToMessengerCall.gasPriceBid, defaultGasPrice); - const expectedAbiData = depositBox.methods.setMinimumBridgingDelay(defaultBridgingDelay).encodeABI(); - assert.equal(setDelayCallToMessengerCall.data, expectedAbiData, "xchain message bytes unexpected"); - }); - it("Pause deposits", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, arbitrumMessenger.options.address) - .send({ from: owner }); - - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner }); - - await bridgeAdmin.methods - .setEnableDepositsAndRelays( - chainId, - l1Token.options.address, - false, - defaultL1CallValue, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner, value: defaultL1CallValue }); - const setPauseCallToMessengerCall = l1InboxMock.smocked.createRetryableTicketNoRefundAliasRewrite.calls[0]; - - // Inbox should receive msg.value - assert.equal((await web3.eth.getBalance(l1InboxMock.options.address)).toString(), defaultL1CallValue); - - // Validate xchain message - assert.equal(setPauseCallToMessengerCall.destAddr, depositBoxImpersonator); - assert.equal(setPauseCallToMessengerCall.l2CallValue, "0"); - assert.equal(setPauseCallToMessengerCall.maxSubmissionCost, maxSubmissionCost); - assert.equal(setPauseCallToMessengerCall.excessFeeRefundAddress, owner); - assert.equal(setPauseCallToMessengerCall.callValueRefundAddress, owner); - assert.equal(setPauseCallToMessengerCall.maxGas, defaultGasLimit); - assert.equal(setPauseCallToMessengerCall.gasPriceBid, defaultGasPrice); - const expectedAbiData = depositBox.methods.setEnableDeposits(l2Token, false).encodeABI(); - assert.equal(setPauseCallToMessengerCall.data, expectedAbiData, "xchain message bytes unexpected"); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/BridgeAdmin.js b/packages/core/test/insured-bridge/BridgeAdmin.js deleted file mode 100644 index af2b7ae03b..0000000000 --- a/packages/core/test/insured-bridge/BridgeAdmin.js +++ /dev/null @@ -1,763 +0,0 @@ -const { assert } = require("chai"); -const hre = require("hardhat"); -const { web3 } = hre; -const { didContractThrow, interfaceName, ZERO_ADDRESS } = require("@uma/common"); -const { getContract, assertEventEmitted } = hre; -const { hexToUtf8, utf8ToHex, toWei } = web3.utils; - -// Tested contracts -const MessengerMock = getContract("MessengerMock"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgePool = getContract("BridgePool"); -const Timer = getContract("Timer"); -const Store = getContract("Store"); -const Finder = getContract("Finder"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); -const IdentifierWhitelist = getContract("IdentifierWhitelist"); -const AddressWhitelist = getContract("AddressWhitelist"); -const ERC20 = getContract("ERC20"); - -// Contract objects -let messenger; -let bridgeAdmin; -let finder; -let store; -let depositBox; -let identifierWhitelist; -let collateralWhitelist; -let timer; - -// Test function inputs -const defaultGasLimit = 1_000_000; -const defaultGasPrice = toWei("1", "gwei"); -const defaultL1CallValue = 10_000_000_000; -const maxSubmissionCost = 10_000_000_000; -const l2GasData = [defaultL1CallValue, defaultGasLimit, defaultGasPrice, maxSubmissionCost]; -const defaultIdentifier = utf8ToHex("IS_CROSS_CHAIN_RELAY_VALID"); -const defaultLiveness = 7200; -const defaultProposerBondPct = toWei("0.05"); -const lpFeeRatePerSecond = toWei("0.0000015"); -const defaultBridgingDelay = 60; -const chainId = "111"; -let l1Token; -let l2Token; -let bridgePool; - -describe("BridgeAdmin", () => { - let accounts, owner, rando, rando2, depositBoxImpersonator; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [owner, rando, rando2, depositBoxImpersonator] = accounts; - l1Token = await ERC20.new("", "").send({ from: owner }); - l2Token = rando2; - finder = await Finder.new().send({ from: owner }); - collateralWhitelist = await AddressWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.CollateralWhitelist), collateralWhitelist.options.address) - .send({ from: owner }); - - identifierWhitelist = await IdentifierWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.IdentifierWhitelist), identifierWhitelist.options.address) - .send({ from: owner }); - timer = await Timer.new().send({ from: owner }); - await identifierWhitelist.methods.addSupportedIdentifier(defaultIdentifier).send({ from: owner }); - - // The initialization of the bridge pool requires there to be an address of both the store and the SkinnyOptimisticOracle - // set in the finder. These tests dont use these contracts but there are never the less needed for deployment. - store = await Store.new({ rawValue: "0" }, { rawValue: "0" }, timer.options.address).send({ from: owner }); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: toWei("1") }).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.Store), store.options.address) - .send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.SkinnyOptimisticOracle), rando) - .send({ from: owner }); - }); - beforeEach(async function () { - messenger = await MessengerMock.new().send({ from: owner }); - - bridgeAdmin = await BridgeAdmin.new( - finder.options.address, - defaultLiveness, - defaultProposerBondPct, - defaultIdentifier - ).send({ from: owner }); - - bridgePool = await BridgePool.new( - "LP Token", - "LPT", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, - timer.options.address - ).send({ from: owner }); - - depositBox = await BridgeDepositBox.new( - bridgeAdmin.options.address, - defaultBridgingDelay, - ZERO_ADDRESS, // weth address. Weth mode not used in these tests - ZERO_ADDRESS // timer address - ).send({ from: owner }); - }); - describe("Admin functions", () => { - it("Set deposit contracts", async () => { - const newDepositContract = rando; - const newMessengerContract = rando2; - assert( - await didContractThrow( - bridgeAdmin.methods - .setDepositContract(chainId, newDepositContract, newMessengerContract) - .send({ from: rando }) - ), - "OnlyOwner modifier not enforced" - ); - assert( - await didContractThrow( - bridgeAdmin.methods.setDepositContract(chainId, ZERO_ADDRESS, newMessengerContract).send({ from: owner }) - ), - "Can't set deposit contract to 0x address" - ); - assert( - await didContractThrow( - bridgeAdmin.methods.setDepositContract(chainId, newDepositContract, ZERO_ADDRESS).send({ from: owner }) - ), - "Can't set messenger contract to 0x address" - ); - const txn = await bridgeAdmin.methods - .setDepositContract(chainId, newDepositContract, newMessengerContract) - .send({ from: owner }); - await assertEventEmitted(txn, bridgeAdmin, "SetDepositContracts", (ev) => { - return ( - ev.chainId === chainId && - ev.l2DepositContract === newDepositContract && - ev.l2MessengerContract === newMessengerContract - ); - }); - const newlySetDepositContracts = await bridgeAdmin.methods.depositContracts(chainId).call(); - assert.equal(newlySetDepositContracts.depositContract, newDepositContract); - assert.equal(newlySetDepositContracts.messengerContract, newMessengerContract); - }); - it("Set relay identifier", async () => { - const newIdentifier = utf8ToHex("NEW_IDENTIFIER"); - assert( - await didContractThrow(bridgeAdmin.methods.setIdentifier(newIdentifier).send({ from: rando })), - "OnlyOwner modifier not enforced" - ); - assert( - await didContractThrow(bridgeAdmin.methods.setIdentifier(newIdentifier).send({ from: owner })), - "Identifier must be whitelisted" - ); - await identifierWhitelist.methods.addSupportedIdentifier(newIdentifier).send({ from: owner }); - const txn = await bridgeAdmin.methods.setIdentifier(newIdentifier).send({ from: owner }); - await assertEventEmitted(txn, bridgeAdmin, "SetRelayIdentifier", (ev) => { - return hexToUtf8(ev.identifier) === hexToUtf8(newIdentifier); - }); - assert.equal(hexToUtf8(await bridgeAdmin.methods.identifier().call()), hexToUtf8(newIdentifier)); - }); - it("Set optimistic oracle liveness", async () => { - const newLiveness = 100; - assert( - await didContractThrow(bridgeAdmin.methods.setOptimisticOracleLiveness(newLiveness).send({ from: rando })), - "OnlyOwner modifier not enforced" - ); - - // Liveness too large, must be less than a 5200 weeks. - assert( - await didContractThrow( - bridgeAdmin.methods.setOptimisticOracleLiveness(5200 * 7 * 24 * 60 * 60).send({ from: owner }) - ) - ); - - // Liveness too small, must be positive. - assert(await didContractThrow(bridgeAdmin.methods.setOptimisticOracleLiveness("0").send({ from: owner }))); - - const txn = await bridgeAdmin.methods.setOptimisticOracleLiveness(newLiveness).send({ from: owner }); - await assertEventEmitted(txn, bridgeAdmin, "SetOptimisticOracleLiveness", (ev) => { - return ev.liveness.toString() === newLiveness.toString(); - }); - assert.equal((await bridgeAdmin.methods.optimisticOracleLiveness().call()).toString(), newLiveness.toString()); - }); - it("Set proposer bond", async () => { - const newBond = toWei("0.1"); - assert( - await didContractThrow(bridgeAdmin.methods.setProposerBondPct(newBond).send({ from: rando })), - "OnlyOwner modifier not enforced" - ); - - const txn = await bridgeAdmin.methods.setProposerBondPct(newBond).send({ from: owner }); - await assertEventEmitted(txn, bridgeAdmin, "SetProposerBondPct", (ev) => { - return ev.proposerBondPct.toString() === newBond.toString(); - }); - assert.equal((await bridgeAdmin.methods.proposerBondPct().call()).toString(), newBond.toString()); - }); - it("Transfer ownership of pool admins", async () => { - assert( - await didContractThrow( - bridgeAdmin.methods.transferBridgePoolAdmin([bridgePool.options.address], rando).send({ from: rando }) - ), - "OnlyOwner modifier not enforced" - ); - - // Create a temp bridgePool. - const bridgePool2 = await BridgePool.new( - "LP Token2", - "LPT2", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, - timer.options.address - ).send({ from: owner }); - - assert.equal(await bridgePool.methods.bridgeAdmin().call(), bridgeAdmin.options.address); - - const transferAdminTxn = await bridgeAdmin.methods - .transferBridgePoolAdmin([bridgePool.options.address, bridgePool2.options.address], owner) - .send({ from: owner }); - - // Check for L1 logs and state change - - await assertEventEmitted(transferAdminTxn, bridgeAdmin, "BridgePoolsAdminTransferred", (ev) => { - return ( - ev.bridgePools.length == 2 && - ev.bridgePools[0] == bridgePool.options.address && - ev.bridgePools[1] == bridgePool2.options.address && - ev.newAdmin == owner - ); - }); - - // Both bridge pools should now have the new owner. - assert.equal(await bridgePool.methods.bridgeAdmin().call(), owner); - assert.equal(await bridgePool2.methods.bridgeAdmin().call(), owner); - }); - it("Set LP fee rate %/second", async () => { - const newRate = toWei("0.0000025"); - assert( - await didContractThrow( - bridgeAdmin.methods.setLpFeeRatePerSecond(bridgePool.options.address, newRate).send({ from: rando }) - ), - "OnlyOwner modifier not enforced" - ); - - const txn = await bridgeAdmin.methods - .setLpFeeRatePerSecond(bridgePool.options.address, newRate) - .send({ from: owner }); - - await assertEventEmitted(txn, bridgeAdmin, "SetLpFeeRate", (ev) => { - return ev.bridgePool == bridgePool.options.address && ev.newLpFeeRatePerSecond.toString() == newRate; - }); - assert.equal(await bridgePool.methods.lpFeeRatePerSecond().call(), newRate); - }); - describe("Cross domain Admin functions", () => { - describe("Whitelist tokens", () => { - it("Basic checks", async () => { - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: rando, value: defaultL1CallValue }) - ), - "OnlyOwner modifier not enforced" - ); - - // Fails if depositContracts not set in BridgeRouter - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "Deposit contract not set" - ); - - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - - // Fails if l1 token is not whitelisted - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "L1 token is not whitelisted collateral" - ); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - // Fails if l2 token address is invalid - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - ZERO_ADDRESS, - bridgePool.options.address, - ...l2GasData - ) - .send({ from: owner, value: defaultL1CallValue }) - ), - "L2 token cannot be zero address" - ); - - // Fails if bridge pool is zero address. - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, ZERO_ADDRESS, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "BridgePool cannot be zero address" - ); - - // Fails if msg.value != defaultL1CallValue. - assert( - await didContractThrow( - bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner }) - ), - "msg.value != defaultL1CallValue" - ); - - // Works if msg.value = 0 and defaultL1CallValue = 0 - assert.ok( - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .call({ from: owner }) - ); - - // Successful call - await bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Messenger should receive msg.value. - assert.equal((await web3.eth.getBalance(messenger.options.address)).toString(), defaultL1CallValue); - - const tokenMapping = await bridgeAdmin.methods.whitelistedTokens(l1Token.options.address, chainId).call(); - assert.isTrue( - tokenMapping.l2Token === l2Token && tokenMapping.bridgePool === bridgePool.options.address, - "Token mapping not created correctly" - ); - }); - it("Add token mapping on L1 and sends xchain message", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - const whitelistTxn = await bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Check for L1 logs and state change - await assertEventEmitted(whitelistTxn, bridgeAdmin, "WhitelistToken", (ev) => { - return ( - ev.chainId === chainId && - ev.l1Token === l1Token.options.address && - ev.l2Token === l2Token && - ev.bridgePool === bridgePool.options.address - ); - }); - const tokenMapping = await bridgeAdmin.methods.whitelistedTokens(l1Token.options.address, chainId).call(); - assert.isTrue( - tokenMapping.l2Token === l2Token && tokenMapping.bridgePool === bridgePool.options.address, - "Token mapping not created correctly" - ); - - // Validate xchain message - const expectedAbiData = depositBox.methods - .whitelistToken(l1Token.options.address, l2Token, bridgePool.options.address) - .encodeABI(); - await assertEventEmitted(whitelistTxn, messenger, "RelayedMessage", (ev) => { - return ( - ev.target === depositBoxImpersonator && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.message === expectedAbiData - ); - }); - }); - it("Can whitelist multiple L2 tokens for one L1 token and bridgePool pair", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - // Whitelist multiple L2 tokens for the one L1 tokens and bridge pool. - await bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - // Create a new L2 token address to mock being having this pool serve multiple L2s. EG USDC on Arbitrum and - // Optimism. This will have the same L1 bridge pool, multiple L2Tokens and bridge deposit boxes (for each L2). - // use a fake address to pretend to be the depositBoxImpersonator for the second chainId - const l2Token2 = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - const chainId2 = chainId + 1; - const depositBoxImpersonator2 = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - - await bridgeAdmin.methods - .setDepositContract(chainId2, depositBoxImpersonator2, messenger.options.address) - .send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken(chainId2, l1Token.options.address, l2Token2, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - const tokenMapping1 = await bridgeAdmin.methods.whitelistedTokens(l1Token.options.address, chainId).call(); - assert.isTrue( - tokenMapping1.l2Token === l2Token && tokenMapping1.bridgePool === bridgePool.options.address, - "Token mapping not created correctly" - ); - - const tokenMapping2 = await bridgeAdmin.methods.whitelistedTokens(l1Token.options.address, chainId2).call(); - assert.isTrue( - tokenMapping2.l2Token === l2Token2 && tokenMapping2.bridgePool === bridgePool.options.address, - "Token mapping not created correctly" - ); - }); - it("Can use whitelist to update the address of the bridge pool on L2 for a given deposit box", async () => { - // The whitelistToken method has a secondary function in providing the ability to update the address of the - // bridge pool for a given L2 chain by re-whitelisting the same L2 token with a new bridge pool address. - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - // Whitelist multiple L2 tokens for the one L1 tokens and bridge pool. - await bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Now consider the case where we have change the address of the bridge pool on L1 due to an upgrade. We now - // need to send messages to each L2 deposit box to update this address. Create a new fake bridgePool and re - // whitelist the same l1Token/l2Token pair on the new pool address. - const bridgePool2 = await BridgePool.new( - "LP Token2", - "LPT2", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, - timer.options.address - ).send({ from: owner }); - const whitelistTxn = await bridgeAdmin.methods - .whitelistToken(chainId, l1Token.options.address, l2Token, bridgePool2.options.address, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // After this whitelist call the address in the bridge admin should have been updated as expected and the - // messenger should have sent the call to update the latest bridgePool. - const tokenMapping = await bridgeAdmin.methods.whitelistedTokens(l1Token.options.address, chainId).call(); - assert.isTrue( - tokenMapping.l2Token === l2Token && tokenMapping.bridgePool === bridgePool2.options.address, - "Token mapping not created correctly" - ); - - // Validate xchain message correctly updates to the new bridgePool2 address - const expectedAbiData = depositBox.methods - .whitelistToken(l1Token.options.address, l2Token, bridgePool2.options.address) - .encodeABI(); - await assertEventEmitted(whitelistTxn, messenger, "RelayedMessage", (ev) => { - return ( - ev.target === depositBoxImpersonator && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.message === expectedAbiData - ); - }); - }); - }); - describe("Set bridge admin", () => { - it("Basic checks", async () => { - assert( - await didContractThrow( - bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, ...l2GasData) - .send({ from: rando, value: defaultL1CallValue }) - ), - "OnlyOwner modifier not enforced" - ); - - // Fails if depositContract not set in BridgeRouter - assert( - await didContractThrow( - bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "Deposit contract not set" - ); - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - - // Admin cannot be 0x0 - assert( - await didContractThrow( - bridgeAdmin.methods - .setCrossDomainAdmin(chainId, ZERO_ADDRESS, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "Cannot set to 0 address" - ); - - // Fails if msg.value != defaultL1CallValue. - assert( - await didContractThrow( - bridgeAdmin.methods.setCrossDomainAdmin(chainId, rando, ...l2GasData).send({ from: owner }) - ), - "msg.value != defaultL1CallValue" - ); - - // Works if msg.value = 0 and defaultL1CallValue = 0 - assert.ok( - await bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, 0, defaultGasLimit, defaultGasPrice, maxSubmissionCost) - .call({ from: owner }) - ); - - // Successful call - await bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Messenger should receive msg.value. - assert.equal((await web3.eth.getBalance(messenger.options.address)).toString(), defaultL1CallValue); - }); - it("Changes admin address", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - const setAdminTxn = await bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Check for L1 logs and state change - await assertEventEmitted(setAdminTxn, bridgeAdmin, "SetCrossDomainAdmin", (ev) => { - return ev.newAdmin === rando && ev.chainId === chainId; - }); - - // Validate xchain message - const expectedAbiData = depositBox.methods.setCrossDomainAdmin(rando).encodeABI(); - await assertEventEmitted(setAdminTxn, messenger, "RelayedMessage", (ev) => { - return ( - ev.target === depositBoxImpersonator && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.message === expectedAbiData - ); - }); - }); - }); - describe("Set minimum bridge delay", () => { - it("Basic checks", async () => { - assert( - await didContractThrow( - bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, ...l2GasData) - .send({ from: rando, value: defaultL1CallValue }) - ), - "OnlyOwner modifier not enforced" - ); - - // Fails if depositContract not set in BridgeRouter - assert( - await didContractThrow( - bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "Deposit contract not set" - ); - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - - // Fails if msg.value != defaultL1CallValue. - assert( - await didContractThrow( - bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, ...l2GasData) - .send({ from: owner }) - ), - "msg.value != defaultL1CallValue" - ); - - // Works if msg.value = 0 and defaultL1CallValue = 0 - assert.ok( - await bridgeAdmin.methods - .setMinimumBridgingDelay( - chainId, - defaultBridgingDelay, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .call({ from: owner }) - ); - - // Successful call - await bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Messenger should receive msg.value. - assert.equal((await web3.eth.getBalance(messenger.options.address)).toString(), defaultL1CallValue); - }); - it("Sets delay", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - const setDelayTxn = await bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Check for L1 logs and state change - await assertEventEmitted(setDelayTxn, bridgeAdmin, "SetMinimumBridgingDelay", (ev) => { - return ev.newMinimumBridgingDelay.toString() === defaultBridgingDelay.toString() && ev.chainId === chainId; - }); - - // Validate xchain message - const expectedAbiData = depositBox.methods.setMinimumBridgingDelay(defaultBridgingDelay).encodeABI(); - await assertEventEmitted(setDelayTxn, messenger, "RelayedMessage", (ev) => { - return ( - ev.target === depositBoxImpersonator && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.message === expectedAbiData - ); - }); - }); - }); - describe("Pause deposits", () => { - it("Basic checks", async () => { - assert( - await didContractThrow( - bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, ...l2GasData) - .send({ from: rando, value: defaultL1CallValue }) - ), - "OnlyOwner modifier not enforced" - ); - - // Fails if depositContract not set in BridgeRouter - assert( - await didContractThrow( - bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }) - ), - "Deposit contract not set" - ); - - // Fails if msg.value != defaultL1CallValue. - assert( - await didContractThrow( - bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, ...l2GasData) - .send({ from: owner }) - ), - "msg.value != defaultL1CallValue" - ); - - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner }); - - // Works if msg.value = 0 and defaultL1CallValue = 0 - assert.ok( - await bridgeAdmin.methods - .setEnableDepositsAndRelays( - chainId, - l1Token.options.address, - false, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .call({ from: owner }) - ); - - // Successful call - await bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Messenger should receive msg.value. - assert.equal((await web3.eth.getBalance(messenger.options.address)).toString(), defaultL1CallValue); - - // Associated bridge pool should be paused. - assert.isFalse(await bridgePool.methods.relaysEnabled().call()); - }); - it("Sets boolean value", async () => { - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, messenger.options.address) - .send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - maxSubmissionCost - ) - .send({ from: owner }); - - const pauseTxn = await bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, ...l2GasData) - .send({ from: owner, value: defaultL1CallValue }); - - // Check for L1 logs and state change - await assertEventEmitted(pauseTxn, bridgeAdmin, "DepositsEnabled", (ev) => { - return Boolean(ev.depositsEnabled) === false && ev.l2Token === l2Token && ev.chainId === chainId; - }); - - // Validate xchain message - const expectedAbiData = depositBox.methods.setEnableDeposits(l2Token, false).encodeABI(); - await assertEventEmitted(pauseTxn, messenger, "RelayedMessage", (ev) => { - return ( - ev.target === depositBoxImpersonator && - ev.gasLimit === defaultGasLimit.toString() && - ev.gasPrice === defaultGasPrice && - ev.message === expectedAbiData - ); - }); - }); - }); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/BridgeDepositBox.js b/packages/core/test/insured-bridge/BridgeDepositBox.js deleted file mode 100644 index f406c1f45e..0000000000 --- a/packages/core/test/insured-bridge/BridgeDepositBox.js +++ /dev/null @@ -1,292 +0,0 @@ -// These tests are meant to be run within the `hardhat` network (not OVM/AVM). They test the bridge deposit box logic -// and ignore all l2/l1 cross chain admin logic. For those tests see AVM_BridgeDepositBox & OVM_BridgeDepositBox for -// L2 specific unit tests that valid logic pertaining to those chains. - -const hre = require("hardhat"); -const { getContract, assertEventEmitted } = hre; -const { assert } = require("chai"); -const { web3 } = hre; -const { toWei, toChecksumAddress, randomHex } = web3.utils; -const { didContractThrow } = require("@uma/common"); - -// Tested contract -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); - -// Helper contracts -const Weth9 = getContract("WETH9"); -const Token = getContract("ExpandedERC20"); -const Timer = getContract("Timer"); - -// Contract objects -let depositBox, l2Token, timer; - -// As these tests are in the context of l2, we dont have the deployed notion of an "L1 Token". The L1 token is within -// another domain (L1). To represent this, we can generate a random address to represent the L1 token. -const l1TokenAddress = toChecksumAddress(randomHex(20)); - -// Create a random address to represent WETH on L1. -const l1WethAddress = toChecksumAddress(randomHex(20)); - -const minimumBridgingDelay = 60; // L2->L1 token bridging must wait at least this time. -const depositAmount = toWei("50"); -const slowRelayFeePct = toWei("0.005"); -const instantRelayFeePct = toWei("0.005"); -const quoteTimestampOffset = 60; // 60 seconds into the past. - -describe("BridgeDepositBox", () => { - let accounts, deployer, user1, bridgeAdmin, bridgePool, l2Weth; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [deployer, user1, bridgeAdmin, bridgePool] = accounts; - - timer = await Timer.new().send({ from: deployer }); - }); - describe("Box deposit logic", () => { - beforeEach(async function () { - depositBox = await BridgeDepositBox.new( - bridgeAdmin, - minimumBridgingDelay, - l1WethAddress, - timer.options.address - ).send({ from: deployer }); - - l2Token = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token.methods.addMember(1, deployer).send({ from: deployer }); - - await l2Token.methods.mint(user1, toWei("100")).send({ from: deployer }); - - await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: bridgeAdmin }); - }); - describe("ERC20 deposit logic", () => { - it("Token flow, events and actions occur correctly on deposit", async () => { - assert.equal(await depositBox.methods.numberOfDeposits().call(), "0"); // Deposit index should start at 0. - - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - - assert.equal((await l2Token.methods.balanceOf(depositBox.options.address).call()).toString(), "0"); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - const tx = await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - assert.equal((await l2Token.methods.balanceOf(depositBox.options.address).call()).toString(), depositAmount); - - await assertEventEmitted(tx, depositBox, "FundsDeposited", (ev) => { - return ( - ev.chainId == "10" && - ev.depositId == "0" && - ev.l1Recipient == user1 && - ev.l2Sender == user1 && - ev.l1Token == l1TokenAddress && - ev.l2Token == l2Token.options.address && - ev.amount == depositAmount && - ev.slowRelayFeePct == slowRelayFeePct && - ev.instantRelayFeePct == instantRelayFeePct && - ev.quoteTimestamp == quoteTimestamp - ); - }); - - assert.equal(await depositBox.methods.numberOfDeposits().call(), "1"); // Deposit index should increment to 1. - }); - }); - - describe("Eth deposit logic", () => { - beforeEach(async function () { - // Depositing with a msg.value should wrap ETH into this weth token, which should be bridged. - l2Weth = await Weth9.new().send({ from: deployer }); - - // Whitelist the l1WethAddress as the L1 token. to indicate to the contract that this is WETH and should be - // withdrawn on L1 as an ETH transfer to the recipient. - await depositBox.methods - .whitelistToken(l1WethAddress, l2Weth.options.address, bridgePool) - .send({ from: bridgeAdmin }); - - // Contract has no weth before and no eth balance. - assert.equal((await l2Weth.methods.balanceOf(depositBox.options.address).call()).toString(), "0"); - assert.equal((await web3.eth.getBalance(depositBox.options.address)).toString(), "0"); - }); - - it("Can correctly deposit ETH, which is wrapped to WETH and bridged as a normal token", async () => { - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - const tx = await depositBox.methods - .deposit(user1, l2Weth.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1, value: depositAmount }); - - // Contract should have depositAmount of weth after the deposit call and no eth (it was wrapped). - assert.equal((await l2Weth.methods.balanceOf(depositBox.options.address).call()).toString(), depositAmount); - assert.equal((await web3.eth.getBalance(depositBox.options.address)).toString(), "0"); - - await assertEventEmitted(tx, depositBox, "FundsDeposited", (ev) => { - return ( - ev.chainId == "10" && - ev.depositId == "0" && - ev.l1Recipient == user1 && - ev.l2Sender == user1 && - ev.l1Token == l1WethAddress && - ev.l2Token == l2Weth.options.address && - ev.amount == depositAmount && - ev.slowRelayFeePct == slowRelayFeePct && - ev.instantRelayFeePct == instantRelayFeePct && - ev.quoteTimestamp == quoteTimestamp - ); - }); - - assert.equal(await depositBox.methods.numberOfDeposits().call(), "1"); // Deposit index should increment to 1. - }); - - it("Can correctly deposit WETH, which is treated as a normal ERC20 token and bridged", async () => { - // Deposit eth into Weth for the user. - await l2Weth.methods.deposit().send({ from: user1, value: depositAmount }); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - - // Send the deposit tx. this time the `value` is 0. Contract should pull the users WETH amount. - await l2Weth.methods.approve(depositBox.options.address, depositAmount).send({ from: user1 }); - const tx = await depositBox.methods - .deposit(user1, l2Weth.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Contract should have depositAmount of weth after the deposit call and no eth (it was wrapped). - assert.equal((await l2Weth.methods.balanceOf(depositBox.options.address).call()).toString(), depositAmount); - assert.equal((await web3.eth.getBalance(depositBox.options.address)).toString(), "0"); - - await assertEventEmitted(tx, depositBox, "FundsDeposited", (ev) => { - return ( - ev.chainId == "10" && - ev.depositId == "0" && - ev.l1Recipient == user1 && - ev.l2Sender == user1 && - ev.l1Token == l1WethAddress && - ev.l2Token == l2Weth.options.address && - ev.amount == depositAmount && - ev.slowRelayFeePct == slowRelayFeePct && - ev.instantRelayFeePct == instantRelayFeePct && - ev.quoteTimestamp == quoteTimestamp - ); - }); - - assert.equal(await depositBox.methods.numberOfDeposits().call(), "1"); // Deposit index should increment to 1. - }); - it("Reverts on amount/msg.value mismatch for Eth deposits", async () => { - // If the user wants to deposit eth but the amount does not match to this value, the tx should revert. - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) - quoteTimestampOffset; - assert( - await didContractThrow( - depositBox.methods - .deposit( - user1, - l2Weth.options.address, - 100, // some value different to depositAmount - slowRelayFeePct, - instantRelayFeePct, - quoteTimestamp - ) - .send({ from: user1, value: depositAmount }) - ) - ); - }); - }); - - describe("Access control and bad deposit checks", () => { - it("Reverts on non-whitelisted token", async () => { - const l2Token_nonWhitelisted = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.addMember(1, deployer).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.mint(user1, toWei("100")).send({ from: deployer }); - - await l2Token_nonWhitelisted.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - assert( - await didContractThrow( - depositBox.methods - .deposit( - user1, - l2Token_nonWhitelisted.options.address, - depositAmount, - slowRelayFeePct, - instantRelayFeePct, - quoteTimestamp - ) - .send({ from: user1 }) - ) - ); - }); - it("Reverts if deposits disabled", async () => { - // Disable deposits - await depositBox.methods.setEnableDeposits(l2Token.options.address, false).send({ from: bridgeAdmin }); - - // Try to deposit and check it reverts. - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - assert( - await didContractThrow( - depositBox.methods - .deposit( - user1, - l2Token.options.address, - depositAmount, - slowRelayFeePct, - instantRelayFeePct, - quoteTimestamp - ) - .send({ from: user1 }) - ) - ); - }); - it("Reverts if slow and instant relay fees exceed individually exceed 25%", async () => { - // Try to deposit and check it reverts. - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - assert( - await didContractThrow( - depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, toWei("0.24"), toWei("0.26"), quoteTimestamp) - .send({ from: user1 }) - ) - ); - assert( - await didContractThrow( - depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, toWei("0.26"), toWei("0.24"), quoteTimestamp) - .send({ from: user1 }) - ) - ); - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, toWei("0.24"), toWei("0.24"), quoteTimestamp) - .send({ from: user1 }); - }); - }); - - describe("Basic checks", () => { - it("canBridge and isWhitelistToken", async () => { - assert.equal(await depositBox.methods.isWhitelistToken(l1TokenAddress).call(), false); - assert.equal( - await depositBox.methods.canBridge(l1TokenAddress).call(), - false, - "Should return false for non-whitelisted token" - ); - assert.equal(await depositBox.methods.isWhitelistToken(l2Token.options.address).call(), true); - assert.equal( - await depositBox.methods.canBridge(l2Token.options.address).call(), - false, - "Should return false for whitelisted with not enough time elapsed since whitelisting" - ); - - // Advance time past minimum bridging delay and then try again - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay + 1) - .send({ from: deployer }); - assert.equal(await depositBox.methods.canBridge(l2Token.options.address).call(), true); - assert.equal( - await depositBox.methods.canBridge(l1TokenAddress).call(), - false, - "Should return false for non-whitelisted token" - ); - }); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/BridgePool.js b/packages/core/test/insured-bridge/BridgePool.js deleted file mode 100644 index ca019a8cbb..0000000000 --- a/packages/core/test/insured-bridge/BridgePool.js +++ /dev/null @@ -1,2902 +0,0 @@ -const { assert } = require("chai"); -const hre = require("hardhat"); -const { web3 } = hre; -const { - didContractThrow, - interfaceName, - TokenRolesEnum, - InsuredBridgeRelayStateEnum, - ZERO_ADDRESS, - MAX_UINT_VAL, -} = require("@uma/common"); -const { getContract, assertEventEmitted } = hre; -const { hexToUtf8, utf8ToHex, toWei, toBN, soliditySha3 } = web3.utils; - -// Tested contracts -const BridgePool = getContract("BridgePool"); - -// Helper contracts -const MessengerMock = getContract("MessengerMock"); -const BridgeAdmin = getContract("BridgeAdmin"); -const Finder = getContract("Finder"); -const IdentifierWhitelist = getContract("IdentifierWhitelist"); -const AddressWhitelist = getContract("AddressWhitelist"); -const OptimisticOracle = getContract("SkinnyOptimisticOracle"); -const Store = getContract("Store"); -const ERC20 = getContract("ExpandedERC20"); -const WETH9 = getContract("WETH9"); -const Timer = getContract("Timer"); -const MockOracle = getContract("MockOracleAncillary"); - -// Contract objects -let messenger; -let bridgeAdmin; -let bridgePool; -let finder; -let store; -let identifierWhitelist; -let collateralWhitelist; -let timer; -let optimisticOracle; -let l1Token; -let l2Token; -let lpToken; -let mockOracle; -let weth; - -// Hard-coded test params: -const defaultRelayHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; -const chainId = "10"; -const defaultGasLimit = 1_000_000; -const defaultGasPrice = toWei("1", "gwei"); -const defaultIdentifier = utf8ToHex("IS_CROSS_CHAIN_RELAY_VALID"); -const defaultLiveness = 100; -const lpFeeRatePerSecond = toWei("0.0000015"); -const defaultProposerBondPct = toWei("0.05"); -const defaultSlowRelayFeePct = toWei("0.01"); -const defaultInstantRelayFeePct = toWei("0.01"); -const defaultQuoteTimestamp = "100000"; // no validation of this happens on L1. -const defaultRealizedLpFee = toWei("0.1"); -const finalFee = toWei("1"); -const initialPoolLiquidity = toWei("1000"); -const relayAmount = toBN(initialPoolLiquidity) - .mul(toBN(toWei("0.1"))) - .div(toBN(toWei("1"))) - .toString(); -const realizedLpFeeAmount = toBN(defaultRealizedLpFee) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))); -const realizedSlowRelayFeeAmount = toBN(defaultSlowRelayFeePct) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))); -const realizedInstantRelayFeeAmount = toBN(defaultInstantRelayFeePct) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))); -const slowRelayAmountSubFee = toBN(relayAmount).sub(realizedLpFeeAmount).sub(realizedSlowRelayFeeAmount).toString(); -const instantRelayAmountSubFee = toBN(relayAmount) - .sub(realizedLpFeeAmount) - .sub(realizedSlowRelayFeeAmount) - .sub(realizedInstantRelayFeeAmount) - .toString(); -// Relayers must post proposal bond + final fee -const proposalBond = toBN(defaultProposerBondPct) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))); -const totalRelayBond = proposalBond.add(toBN(finalFee)); -// Winner of a dispute gets bond back + 1/2 of loser's bond+final fee. So, the total dispute refund is -// 1.5x the proposer bond + final fee. -const totalDisputeRefund = toBN(defaultProposerBondPct) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))) - .mul(toBN(toWei("1.5"))) - .div(toBN(toWei("1"))) - .add(toBN(finalFee)); -// Forfeited dispute bond + final fee is paid to store. -const disputePaidToStore = toBN(defaultProposerBondPct) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))) - .mul(toBN(toWei("0.5"))) - .div(toBN(toWei("1"))) - .add(toBN(finalFee)); - -// Conveniently re-used values: -let defaultRelayData; -let defaultDepositData; -let defaultDepositHash; -let defaultRelayAncillaryData; - -describe("BridgePool", () => { - let accounts, - owner, - depositContractImpersonator, - depositor, - relayer, - liquidityProvider, - l1Recipient, - instantRelayer, - disputer, - rando; - - const advanceTime = async (timeIncrease) => { - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + timeIncrease) - .send({ from: owner }); - }; - - // Construct params from relayDeposit. Uses the default `depositData` as the base information while enabling the - // caller to override specific values for either the deposit data or relay data. - const generateRelayParams = (depositDataOverride = {}, relayDataOverride = {}) => { - const _depositData = { ...defaultDepositData, ...depositDataOverride }; - const _relayData = { ...defaultRelayData, ...relayDataOverride }; - return [_depositData, _relayData.realizedLpFeePct]; - }; - - // Generate ABI encoded deposit data and deposit data hash. - const generateDepositHash = (_depositData) => { - const depositDataAbiEncoded = web3.eth.abi.encodeParameters( - ["uint256", "uint64", "address", "address", "uint256", "uint64", "uint64", "uint64", "address"], - [ - _depositData.chainId, - _depositData.depositId, - _depositData.l1Recipient, - _depositData.l2Sender, - _depositData.amount, - _depositData.slowRelayFeePct, - _depositData.instantRelayFeePct, - _depositData.quoteTimestamp, - l1Token.options.address, - ] - ); - const depositHash = soliditySha3(depositDataAbiEncoded); - return depositHash; - }; - - // Return hash of deposit data and instant relay params that BridgePool stores in state. - const generateInstantRelayHash = (_depositHash, _relayData) => { - const instantRelayDataAbiEncoded = web3.eth.abi.encodeParameters( - ["bytes32", "uint64"], - [_depositHash, _relayData.realizedLpFeePct] - ); - return soliditySha3(instantRelayDataAbiEncoded); - }; - - // Return hash of relay data that BridgePool stores in state. - const generateRelayHash = (_relayData) => { - return soliditySha3( - web3.eth.abi.encodeParameters( - ["uint256", "address", "uint32", "uint64", "uint256", "uint256", "uint256"], - [ - _relayData.relayState, - _relayData.slowRelayer, - _relayData.relayId, - _relayData.realizedLpFeePct, - _relayData.priceRequestTime, - _relayData.proposerBond, - _relayData.finalFee, - ] - ) - ); - }; - - // Replicate the hashed ancillary data that is returned by BridgePool's internal _getRelayHash() method. - const generateRelayAncillaryDataHash = (_depositData, _relayData) => { - const parameters = [ - { t: "uint256", v: _depositData.chainId }, - { t: "uint64", v: _depositData.depositId }, - { t: "address", v: _depositData.l1Recipient }, - { t: "address", v: _depositData.l2Sender }, - { t: "uint256", v: _depositData.amount }, - { t: "uint64", v: _depositData.slowRelayFeePct }, - { t: "uint64", v: _depositData.instantRelayFeePct }, - { t: "uint64", v: _depositData.quoteTimestamp }, - { t: "uint32", v: _relayData.relayId }, - { t: "uint64", v: _relayData.realizedLpFeePct }, - { t: "address", v: l1Token.options.address }, - ]; - return web3.utils.soliditySha3( - web3.eth.abi.encodeParameters( - parameters.map((elt) => elt.t), - parameters.map((elt) => elt.v) - ) - ); - }; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [ - owner, - depositContractImpersonator, - depositor, - relayer, - liquidityProvider, - l1Recipient, - l2Token, - instantRelayer, - disputer, - rando, - ] = accounts; - - // Deploy or fetch deployed contracts: - finder = await Finder.new().send({ from: owner }); - collateralWhitelist = await AddressWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.CollateralWhitelist), collateralWhitelist.options.address) - .send({ from: owner }); - - identifierWhitelist = await IdentifierWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.IdentifierWhitelist), identifierWhitelist.options.address) - .send({ from: owner }); - timer = await Timer.new().send({ from: owner }); - store = await Store.new({ rawValue: "0" }, { rawValue: "0" }, timer.options.address).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.Store), store.options.address) - .send({ from: owner }); - - // Other contract setup needed to relay deposit: - await identifierWhitelist.methods.addSupportedIdentifier(defaultIdentifier).send({ from: owner }); - }); - beforeEach(async function () { - // Deploy new contracts with clean state and perform setup: - l1Token = await ERC20.new("TESTERC20", "TESTERC20", 18).send({ from: owner }); - await l1Token.methods.addMember(TokenRolesEnum.MINTER, owner).send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: finalFee }).send({ from: owner }); - - // Deploy new OptimisticOracle so that we can control its timing: - // - Set initial liveness to something != `defaultLiveness` so we can test that the custom liveness is set - // correctly by the BridgePool. - optimisticOracle = await OptimisticOracle.new( - defaultLiveness * 10, - finder.options.address, - timer.options.address - ).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.SkinnyOptimisticOracle), optimisticOracle.options.address) - .send({ from: owner }); - - // Deploy new MockOracle so that OptimisticOracle disputes can make price requests to it: - mockOracle = await MockOracle.new(finder.options.address, timer.options.address).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.Oracle), mockOracle.options.address) - .send({ from: owner }); - - // Deploy and setup BridgeAdmin: - messenger = await MessengerMock.new().send({ from: owner }); - bridgeAdmin = await BridgeAdmin.new( - finder.options.address, - defaultLiveness, - defaultProposerBondPct, - defaultIdentifier - ).send({ from: owner }); - await bridgeAdmin.methods - .setDepositContract(chainId, depositContractImpersonator, messenger.options.address) - .send({ from: owner }); - - // New BridgePool linked to BridgeAdmin - bridgePool = await BridgePool.new( - "LP Token", - "LPT", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, // this is not a weth pool (contains normal ERC20) - timer.options.address - ).send({ from: owner }); - - // The bridge pool has an embedded ERC20 to represent LP positions. - lpToken = await ERC20.at(bridgePool.options.address); - - // Add L1-L2 token mapping - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - 0 - ) - .send({ from: owner }); - - // Seed relayers, and disputer with tokens. - await l1Token.methods.mint(relayer, totalRelayBond).send({ from: owner }); - await l1Token.methods.mint(disputer, totalRelayBond).send({ from: owner }); - await l1Token.methods.mint(instantRelayer, instantRelayAmountSubFee).send({ from: owner }); - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - - // Store default deposit and relay data that we'll use to verify contract state: - defaultDepositData = { - chainId: chainId, - depositId: 1, - l1Recipient: l1Recipient, - l2Sender: depositor, - amount: relayAmount, - slowRelayFeePct: defaultSlowRelayFeePct, - instantRelayFeePct: defaultInstantRelayFeePct, - quoteTimestamp: defaultQuoteTimestamp, - }; - defaultRelayData = { - relayState: InsuredBridgeRelayStateEnum.UNINITIALIZED, - relayId: 0, - priceRequestTime: 0, - realizedLpFeePct: defaultRealizedLpFee, - slowRelayer: relayer, - finalFee: finalFee, - proposerBond: proposalBond.toString(), - }; - - // Save other reused values. - defaultDepositHash = generateDepositHash(defaultDepositData); - defaultRelayAncillaryData = await bridgePool.methods - .getRelayAncillaryData(defaultDepositData, defaultRelayData) - .call(); - }); - it("Constructor validation", async function () { - // LP Token symbol and name cannot be empty. - assert( - await didContractThrow( - BridgePool.new( - "", - "LPT", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, // this is not a weth pool (contains normal ERC20) - timer.options.address - ).send({ from: owner }) - ) - ); - assert( - await didContractThrow( - BridgePool.new( - "LP Token", - "", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, // this is not a weth pool (contains normal ERC20) - timer.options.address - ).send({ from: owner }) - ) - ); - }); - describe("Bridge Admin functionality", () => { - it("Transferring Bridge Admin", async function () { - // Admin can only be transferred by current admin. - assert.equal(await bridgePool.methods.bridgeAdmin().call(), bridgeAdmin.options.address); - - assert( - await didContractThrow( - bridgeAdmin.methods.transferBridgePoolAdmin([bridgePool.options.address], rando).send({ from: rando }) - ) - ); - - // Calling from the correct address can transfer ownership. - const tx = await bridgeAdmin.methods - .transferBridgePoolAdmin([bridgePool.options.address], owner) - .send({ from: owner }); - - assert.equal(await bridgePool.methods.bridgeAdmin().call(), owner); - - await assertEventEmitted(tx, bridgePool, "BridgePoolAdminTransferred", (ev) => { - return ev.oldAdmin === bridgeAdmin.options.address && ev.newAdmin === owner; - }); - }); - it("Lp Fee %/second", async function () { - // Can only be set by current admin. - const newRate = toWei("0.0000025"); - assert( - await didContractThrow( - bridgeAdmin.methods.setLpFeeRatePerSecond(bridgePool.options.address, newRate).send({ from: rando }) - ) - ); - - // Calling from the correct address succeeds. - const tx = await bridgeAdmin.methods - .setLpFeeRatePerSecond(bridgePool.options.address, newRate) - .send({ from: owner }); - - assert.equal(await bridgePool.methods.lpFeeRatePerSecond().call(), newRate); - - await assertEventEmitted(tx, bridgePool, "LpFeeRateSet", (ev) => { - return ev.newLpFeeRatePerSecond.toString() === newRate; - }); - }); - it("Constructs utf8-encoded ancillary data for relay", async function () { - assert.equal( - hexToUtf8(defaultRelayAncillaryData), - `relayHash:${generateRelayAncillaryDataHash(defaultDepositData, defaultRelayData).substring(2)}` - ); - }); - it("Sync with Finder addresses", async function () { - // Check the sync with finder correctly updates the local instance of important contracts to that in the finder. - assert.equal(await bridgePool.methods.optimisticOracle().call(), optimisticOracle.options.address); - - // Change the address of the OO in the finder to any random address. - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.SkinnyOptimisticOracle), rando) - .send({ from: owner }); - - await bridgePool.methods.syncUmaEcosystemParams().send({ from: rando }); - - // Check it's been updated accordingly - assert.equal(await bridgePool.methods.optimisticOracle().call(), rando); - }); - it("Sync with BridgeAdmin params", async function () { - // Check the sync with bridgeAdmin params correctly updates the local params. - assert.equal( - await bridgePool.methods.proposerBondPct().call(), - await bridgeAdmin.methods.proposerBondPct().call() - ); - - // Change the address of the OO in the finder to any random address. - await bridgeAdmin.methods.setProposerBondPct(toWei("0.06")).send({ from: owner }); - assert.equal(await bridgeAdmin.methods.proposerBondPct().call(), toWei("0.06")); - - await bridgePool.methods.syncWithBridgeAdminParams().send({ from: rando }); - - // Check it's been updated accordingly - assert.equal(await bridgePool.methods.proposerBondPct().call(), toWei("0.06")); - }); - it("BridgeAdmin can pause relays", async function () { - // Before pausing, relays should be enabled. - assert.isTrue(await bridgePool.methods.relaysEnabled().call()); - - // Creating a relay should be posable - - // Add liquidity to the pool - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Mint some tokens to the relayer so they can do relays. - await l1Token.methods.mint(relayer, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: relayer }); - - // Do a relay to show it wont revert. - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Next, the bridge admin calls to pause relays. This should block subsequent deposit relay actions. Note that the - // magic numbers in the function call below are L2->L2 gas params that are not used in these tests. - await bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, 0, 0, 0, 0) - .send({ from: owner }); - - // Now, as paused, both relay and relay and speedup should be disabled. - - await didContractThrow( - bridgePool.methods - .relayAndSpeedUp({ ...defaultDepositData, amount: "2" }, defaultRealizedLpFee) - .send({ from: relayer }) - ); - - await didContractThrow( - bridgePool.methods.relayDeposit(...generateRelayParams({ depositId: 3 })).send({ from: relayer }) - ); - - // re-enabling relays should make the above no longer throw. - await bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, true, 0, 0, 0, 0) - .send({ from: owner }); - - await bridgePool.methods - .relayAndSpeedUp({ ...defaultDepositData, amount: "2" }, defaultRealizedLpFee) - .send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams({ depositId: 3 })).send({ from: relayer }); - }); - }); - describe("Relay deposit", () => { - beforeEach(async function () { - // Add liquidity to the pool - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - }); - it("Basic checks", async () => { - // Fails if approval not given by relayer. - assert(await didContractThrow(bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }))); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - // Note: For the following tests, mint relayer enough balance such that their balance isn't the reason why the - // contract call reverts. - await l1Token.methods.mint(relayer, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: relayer }); - - // Fails if pool doesn't have enough funds to cover reward; request price will revert when it tries to pull reward. - // -setting relay amount to the pool's full balance and the reward % to >100% will induce this - assert( - await didContractThrow( - bridgePool.methods - .relayDeposit(...generateRelayParams({}, { realizedLpFeePct: toWei("1.01") })) - .send({ from: relayer }) - ) - ); - - // Fails if withdrawal amount+proposer reward > pool balance. Setting relay amount to 99% of pool's full - // balance and then reward % to 15%, where the relay amount is already assumed to be 10% of the full balance, - // means that total withdrawal %=(0.99+0.15*0.1) > 1.0 - assert( - await didContractThrow( - bridgePool.methods - .relayDeposit( - ...generateRelayParams( - { - amount: toBN(initialPoolLiquidity) - .mul(toBN(toWei("0.99"))) - .div(toBN(toWei("1"))) - .toString(), - }, - { realizedLpFeePct: toWei("1.15") } - ) - ) - .send({ from: relayer }) - ) - ); - - assert.equal(await bridgePool.methods.numberOfRelays().call(), "0"); // Relay index should start at 0. - - // Deposit with no relay attempt should have correct state and empty relay hash. - const relayStatus = await bridgePool.methods.relays(defaultDepositHash).call(); - assert.equal(relayStatus, defaultRelayHash); - }); - it("Relay checks", async () => { - // Proposer approves pool to withdraw total bond. - // Approve and mint many tokens to the relayer. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: relayer }); - await l1Token.methods.mint(relayer, toWei("100000")).send({ from: owner }); - await bridgePool.methods - .relayDeposit(...generateRelayParams({ amount: toBN(initialPoolLiquidity).subn(1).toString() })) - .send({ from: relayer }); - await didContractThrow( - bridgePool.methods.relayDeposit(...generateRelayParams({ amount: "2" })).send({ from: relayer }) - ); - await didContractThrow( - bridgePool.methods - .relayAndSpeedUp({ ...defaultDepositData, amount: "2" }, defaultRealizedLpFee) - .send({ from: relayer }) - ); - }); - it("Requests and proposes optimistic price request", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - const txn = await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Relay count increments. - assert.equal(await bridgePool.methods.numberOfRelays().call(), "1"); - - // Check L1 token balances. - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - "0", - "Relayer should post entire balance as bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - totalRelayBond.add(toBN(initialPoolLiquidity)), - "OptimisticOracle should custody total relay bond" - ); - - // Check RelayData struct is stored correctly and mapped to the deposit hash. - const relayStatus = await bridgePool.methods.relays(defaultDepositHash).call(); - const relayHash = generateRelayHash(relayAttemptData); - assert.equal(relayStatus, relayHash); - - // Instant relayer for this relay should be uninitialized. - const instantRelayHash = generateInstantRelayHash(defaultDepositHash, relayAttemptData); - assert.equal(await bridgePool.methods.instantRelays(instantRelayHash).call(), ZERO_ADDRESS); - - // Check event is logged correctly and emits all information needed to recreate the relay and associated deposit. - await assertEventEmitted(txn, bridgePool, "DepositRelayed", (ev) => { - return ( - ev.depositHash === defaultDepositHash && - ev.depositData.chainId.toString() === defaultDepositData.chainId.toString() && - ev.depositData.depositId.toString() === defaultDepositData.depositId.toString() && - ev.depositData.l1Recipient === defaultDepositData.l1Recipient && - ev.depositData.l2Sender === defaultDepositData.l2Sender && - ev.depositData.amount === defaultDepositData.amount && - ev.depositData.slowRelayFeePct === defaultDepositData.slowRelayFeePct && - ev.depositData.instantRelayFeePct === defaultDepositData.instantRelayFeePct && - ev.depositData.quoteTimestamp === defaultDepositData.quoteTimestamp && - ev.relay.slowRelayer === relayer && - ev.relay.relayId.toString() === relayAttemptData.relayId.toString() && - ev.relay.realizedLpFeePct === relayAttemptData.realizedLpFeePct && - ev.relay.priceRequestTime === relayAttemptData.priceRequestTime && - ev.relay.relayState === relayAttemptData.relayState && - ev.relayAncillaryDataHash === generateRelayAncillaryDataHash(defaultDepositData, relayAttemptData) - ); - }); - - // Check that another relay with different relay params for the same deposit reverts. - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - let duplicateRelayData = { realizedLpFeePct: toBN(defaultRealizedLpFee).mul(toBN("2")) }; - assert( - await didContractThrow( - bridgePool.methods.relayDeposit(defaultDepositData, duplicateRelayData.realizedLpFeePct).send({ from: rando }) - ) - ); - }); - }); - describe("Speed up relay", () => { - beforeEach(async function () { - // Add liquidity to the pool - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - }); - - it("Valid instant relay, disputed, instant relayer should receive refund following subsequent valid relay", async () => { - // Propose new relay: - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Must approve contract to pull deposit amount. - assert( - await didContractThrow( - bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: instantRelayer }) - ) - ); - await l1Token.methods - .approve(bridgePool.options.address, instantRelayAmountSubFee) - .send({ from: instantRelayer }); - - // Cannot speed up using relay params that do not correspond to pending relay. - assert( - await didContractThrow( - bridgePool.methods.speedUpRelay(defaultDepositData, defaultRelayData).call({ from: instantRelayer }) - ) - ); - - // Can speed up pending relay - const speedupTxn = await bridgePool.methods - .speedUpRelay(defaultDepositData, relayAttemptData) - .send({ from: instantRelayer }); - await assertEventEmitted(speedupTxn, bridgePool, "RelaySpedUp", (ev) => { - return ( - ev.depositHash === defaultDepositHash && - ev.instantRelayer === instantRelayer && - ev.relay.slowRelayer === relayer && - ev.relay.relayId === relayAttemptData.relayId.toString() && - ev.relay.realizedLpFeePct === relayAttemptData.realizedLpFeePct && - ev.relay.priceRequestTime === relayAttemptData.priceRequestTime && - ev.relay.relayState === relayAttemptData.relayState - ); - }); - const speedupRelayStatus = await bridgePool.methods.relays(defaultDepositHash).call(); - assert.equal(speedupRelayStatus, generateRelayHash(relayAttemptData)); - const instantRelayHash = generateInstantRelayHash(defaultDepositHash, relayAttemptData); - assert.equal(await bridgePool.methods.instantRelays(instantRelayHash).call(), instantRelayer); - - // Check that contract pulled relay amount from instant relayer. - assert.equal( - (await l1Token.methods.balanceOf(instantRelayer).call()).toString(), - "0", - "Instant Relayer should transfer relay amount" - ); - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - instantRelayAmountSubFee, - "Recipient should receive the full amount, minus slow & instant fees" - ); - - // Submit dispute. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - - // Cache price request timestamp. - await advanceTime(1); - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - relayAttemptData = { - ...relayAttemptData, - slowRelayer: rando, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Set up a relayAndSpeedUp transaction that should fail due to an existing relay. - await l1Token.methods.mint(rando, totalRelayBond.add(toBN(instantRelayAmountSubFee))).send({ from: owner }); - await l1Token.methods - .approve(bridgePool.options.address, totalRelayBond.add(toBN(instantRelayAmountSubFee))) - .send({ from: rando }); - assert( - await didContractThrow( - bridgePool.methods - .relayAndSpeedUp(defaultDepositData, relayAttemptData.realizedLpFeePct) - .send({ from: rando }) - ) - ); - - // Reset params to allow for a normal relay. - await l1Token.methods.transfer(instantRelayer, instantRelayAmountSubFee).send({ from: rando }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - - // Cannot repeatedly speed relay up. - await l1Token.methods - .approve(bridgePool.options.address, instantRelayAmountSubFee) - .send({ from: instantRelayer }); - assert( - await didContractThrow( - bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: instantRelayer }) - ) - ); - // Burn newly minted tokens to make accounting simpler. - await l1Token.methods.transfer(owner, instantRelayAmountSubFee).send({ from: instantRelayer }); - - // Resolve and settle relay. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - // Expire relay. Since instant relayed amount was correct, instant relayer should be refunded and user should - // still just have the instant relay amount. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - // Check token balances. - // - Slow relayer should get back their proposal bond from OO and reward from BridgePool. - // - Fast relayer should get reward from BridgePool and the relayed amount, minus LP and slow relay fee. This - // is equivalent to what the l1Recipient received + the instant relayer fee. - // - Recipient already got paid by fast relayer and should receive no further tokens. - assert.equal( - (await l1Token.methods.balanceOf(rando).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Slow relayer should receive proposal bond + slow relay reward" - ); - assert.equal( - (await l1Token.methods.balanceOf(instantRelayer).call()).toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString(), - "Instant relayer should receive instant relay reward + the instant relay amount sub fees" - ); - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - instantRelayAmountSubFee, - "Recipient should still have the full amount, minus slow & instant fees" - ); - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - totalDisputeRefund.toString(), - "OptimisticOracle should still hold dispute refund since dispute has not resolved yet" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity) - .sub(toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount)) - .toString(), - "BridgePool should have balance reduced by relayed amount to l1Recipient" - ); - }); - it("Valid slow + instant relay, disputed, instant relayer should receive refund following subsequent valid relay", async () => { - // Propose new relay: - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - - await l1Token.methods - .approve(bridgePool.options.address, totalRelayBond.add(toBN(instantRelayAmountSubFee))) - .send({ from: relayer }); - await l1Token.methods.mint(relayer, instantRelayAmountSubFee).send({ from: owner }); - const speedupTxn = await bridgePool.methods - .relayAndSpeedUp(defaultDepositData, relayAttemptData.realizedLpFeePct) - .send({ from: relayer }); - - await assertEventEmitted(speedupTxn, bridgePool, "RelaySpedUp", (ev) => { - return ( - ev.depositHash === defaultDepositHash && - ev.instantRelayer === relayer && - ev.relay.slowRelayer === relayer && - ev.relay.relayId === relayAttemptData.relayId.toString() && - ev.relay.realizedLpFeePct === relayAttemptData.realizedLpFeePct && - ev.relay.priceRequestTime === relayAttemptData.priceRequestTime && - ev.relay.relayState === relayAttemptData.relayState - ); - }); - const speedupRelayStatus = await bridgePool.methods.relays(defaultDepositHash).call(); - assert.equal(speedupRelayStatus, generateRelayHash(relayAttemptData)); - const instantRelayHash = generateInstantRelayHash(defaultDepositHash, relayAttemptData); - assert.equal(await bridgePool.methods.instantRelays(instantRelayHash).call(), relayer); - - // Check that contract pulled relay amount from instant relayer. - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - "0", - "Instant Relayer should transfer relay amount" - ); - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - instantRelayAmountSubFee, - "Recipient should receive the full amount, minus slow & instant fees" - ); - - // Submit for dispute. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - - // Cache price request timestamp. - await advanceTime(1); - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - relayAttemptData = { - ...relayAttemptData, - slowRelayer: rando, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Set up a relayAndSpeedUp transaction that should fail due to an existing relay. - await l1Token.methods.mint(rando, totalRelayBond.add(toBN(instantRelayAmountSubFee))).send({ from: owner }); - await l1Token.methods - .approve(bridgePool.options.address, totalRelayBond.add(toBN(instantRelayAmountSubFee))) - .send({ from: rando }); - assert( - await didContractThrow( - bridgePool.methods - .relayAndSpeedUp(defaultDepositData, relayAttemptData.realizedLpFeePct) - .send({ from: rando }) - ) - ); - - // Reset params to allow for a normal relay. - await l1Token.methods.transfer(instantRelayer, instantRelayAmountSubFee).send({ from: rando }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - - await bridgePool.methods.relayDeposit(...generateRelayParams({}, relayAttemptData)).send({ from: rando }); - - // Cannot repeatedly speed relay up. - await l1Token.methods - .approve(bridgePool.options.address, instantRelayAmountSubFee) - .send({ from: instantRelayer }); - assert( - await didContractThrow( - bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: instantRelayer }) - ) - ); - // Burn newly minted tokens to make accounting simpler. - await l1Token.methods.transfer(owner, instantRelayAmountSubFee).send({ from: instantRelayer }); - - // Resolve and settle relay. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - // Expire relay. Since instant relayed amount was correct, instant relayer should be refunded and user should - // still just have the instant relay amount. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - // Check token balances. - // - Slow relayer should get back their proposal bond from OO and reward from BridgePool. - // - Fast relayer should get reward from BridgePool and the relayed amount, minus LP and slow relay fee. This - // is equivalent to what the l1Recipient received + the instant relayer fee. - // - Recipient already got paid by fast relayer and should receive no further tokens. - assert.equal( - (await l1Token.methods.balanceOf(rando).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Slow relayer should receive proposal bond + slow relay reward" - ); - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString(), - "Instant relayer should receive instant relay reward + the instant relay amount sub fees" - ); - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - instantRelayAmountSubFee, - "Recipient should still have the full amount, minus slow & instant fees" - ); - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - totalDisputeRefund.toString(), - "OptimisticOracle should still hold dispute refund since dispute has not resolved yet" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity) - .sub(toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount)) - .toString(), - "BridgePool should have balance reduced by relayed amount to l1Recipient" - ); - }); - it("Invalid instant relay, disputed, instant relayer receives no refund following subsequent valid relay", async function () { - // Propose new invalid relay where realizedFee is too large. - const invalidRealizedLpFee = toWei("0.4"); - const invalidRelayData = { - ...defaultRelayData, - realizedLpFeePct: invalidRealizedLpFee, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams({}, invalidRelayData)).send({ from: relayer }); - - // Invalid instant relay with incorrect fee sends incorrect amount to user - const invalidRealizedLpFeeAmount = toBN(invalidRealizedLpFee) - .mul(toBN(relayAmount)) - .div(toBN(toWei("1"))); - const invalidInstantRelayAmountSubFee = toBN(relayAmount) - .sub(invalidRealizedLpFeeAmount) - .sub(realizedSlowRelayFeeAmount) - .sub(realizedInstantRelayFeeAmount) - .toString(); - await l1Token.methods - .approve(bridgePool.options.address, invalidInstantRelayAmountSubFee) - .send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, invalidRelayData).send({ from: instantRelayer }); - const startingInstantRelayerAmount = (await l1Token.methods.balanceOf(instantRelayer).call()).toString(); - - // User receives invalid instant relay amount. - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - invalidInstantRelayAmountSubFee - ); - - // Before slow relay expires, it is disputed. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, invalidRelayData).send({ from: disputer }); - - // While dispute is pending resolution, a valid relay is resubmitted. Advance time so that - // price request data is different. - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - await advanceTime(1); - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - slowRelayer: rando, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - - // Instant relayer address should be empty for most recent relay with valid params. - const instantRelayHash = generateInstantRelayHash(defaultDepositHash, relayAttemptData); - assert.equal(await bridgePool.methods.instantRelays(instantRelayHash).call(), ZERO_ADDRESS); - - // Can speed up relay with new (valid) params. - await l1Token.methods.mint(instantRelayer, instantRelayAmountSubFee).send({ from: owner }); - await l1Token.methods - .approve(bridgePool.options.address, instantRelayAmountSubFee) - .send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // Expire this valid relay and check payouts. - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - // User should receive correct instant relay amount + the amount they received from invalid instant relay. - assert.equal( - (await l1Token.methods.balanceOf(defaultDepositData.l1Recipient).call()).toString(), - toBN(instantRelayAmountSubFee).add(toBN(invalidInstantRelayAmountSubFee)).toString() - ); - - // Invalid instant relayer receives a refund from their correct instant relay, but nothing from their first - // invalid relay that they sped up. - assert.equal( - (await l1Token.methods.balanceOf(instantRelayer).call()).toString(), - toBN(startingInstantRelayerAmount) - .add(toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount)) - .toString() - ); - - // Slow relayer gets slow relay reward - assert.equal( - (await l1Token.methods.balanceOf(rando).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString() - ); - - // OptimisticOracle should still hold dispute refund since dispute has not resolved yet - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - totalDisputeRefund.toString() - ); - - // BridgePool should have balance reduced by full amount sent to to l1Recipient. - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity) - .sub(toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount)) - .toString() - ); - }); - }); - describe("Dispute pending relay", () => { - beforeEach(async function () { - // Add liquidity to the pool - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - }); - it("Can re-relay disputed request", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Dispute bond should be equal to proposal bond, and OptimisticOracle needs to be able to pull dispute bond - // from disputer. - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - const disputeTxn = await bridgePool.methods - .disputeRelay(defaultDepositData, relayAttemptData) - .send({ from: disputer }); - - // Check for expected events: - await assertEventEmitted(disputeTxn, optimisticOracle, "DisputePrice", (ev) => { - return ( - ev.requester === bridgePool.options.address && - hexToUtf8(ev.identifier) === hexToUtf8(defaultIdentifier) && - ev.timestamp.toString() === requestTimestamp && - ev.ancillaryData === defaultRelayAncillaryData && - ev.request.proposer === relayer && - ev.request.disputer === disputer && - ev.request.currency === l1Token.options.address && - !ev.request.settled && - ev.request.proposedPrice === toWei("1") && - ev.request.resolvedPrice === "0" && - ev.request.expirationTime === expectedExpirationTimestamp && - ev.request.reward === "0" && - ev.request.finalFee === finalFee && - ev.request.bond === proposalBond.toString() && - ev.request.customLiveness === defaultLiveness.toString() - ); - }); - - await assertEventEmitted(disputeTxn, bridgePool, "RelayDisputed", (ev) => { - return ( - ev.disputer === disputer && - ev.depositHash === generateDepositHash(defaultDepositData) && - ev.relayHash === generateRelayHash(relayAttemptData) - ); - }); - - // Mint relayer new bond to try relaying again: - await l1Token.methods.mint(relayer, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - // Relaying again with the exact same relay params will succeed because the relay nonce increments. - // This is possible because the `OO.disputePrice` callback to the BridgePool marks the relay as Disputed, - // allowing it to be relayed again. - assert.ok(await bridgePool.methods.relayDeposit(...generateRelayParams()).call({ from: relayer })); - }); - it("Relay request is disputed, re-relay request expires before dispute resolves", async function () { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - slowRelayer: relayer, - }; - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Dispute bond should be equal to proposal bond, and OptimisticOracle needs to be able to pull dispute bond - // from disputer. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - - // Dispute should leave pendingReserves at 0. - assert.equal(await bridgePool.methods.pendingReserves().call(), "0"); - - // Mint new relayer bond to relay again: - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - slowRelayer: rando, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - - // Resolve re-relay. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - // Should not be able to settle relay again even if the OO has a resolved price. - assert( - await didContractThrow( - bridgePool.methods - .settleRelay(defaultDepositData, { ...relayAttemptData, relayState: InsuredBridgeRelayStateEnum.FINALIZED }) - .send({ from: rando }) - ) - ); - - // Now resolve dispute with original relay data. - const price = toWei("1"); - const stampedDisputeAncillaryData = await optimisticOracle.methods - .stampAncillaryData(defaultRelayAncillaryData, bridgePool.options.address) - .call(); - - const proposalEvent = (await optimisticOracle.getPastEvents("ProposePrice", { fromBlock: 0 }))[0]; - await mockOracle.methods - .pushPrice(defaultIdentifier, proposalEvent.returnValues.timestamp, stampedDisputeAncillaryData, price) - .send({ from: owner }); - const disputeEvent = (await optimisticOracle.getPastEvents("DisputePrice", { fromBlock: 0 }))[0]; - await optimisticOracle.methods - .settle( - bridgePool.options.address, - defaultIdentifier, - disputeEvent.returnValues.timestamp, - disputeEvent.returnValues.ancillaryData, - disputeEvent.returnValues.request - ) - .send({ from: accounts[0] }); - - assert.equal( - (await l1Token.methods.balanceOf(rando).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Follow-up relayer should receive proposal bond + slow relay reward" - ); - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - totalDisputeRefund.toString(), - "Original relayer should receive entire bond back + 1/2 of loser's bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity).sub( - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount) - ), - "Pool should have initial liquidity amount minus relay amount sub LP fee" - ); - assert.equal( - (await l1Token.methods.balanceOf(l1Recipient).call()).toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString(), - "Recipient should receive total relay amount + instant relay fee" - ); - }); - it("Relay request is disputed, re-relay request expires after dispute resolves", async function () { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - slowRelayer: relayer, - }; - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Dispute bond should be equal to proposal bond, and OptimisticOracle needs to be able to pull dispute bond - // from disputer. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - const proposalEvent = (await optimisticOracle.getPastEvents("ProposePrice", { fromBlock: 0 }))[0]; - - // Mint new relayer bond to relay again: - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - slowRelayer: rando, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - - // Resolve dispute with original relay data. - const price = toWei("1"); - const stampedDisputeAncillaryData = await optimisticOracle.methods - .stampAncillaryData(defaultRelayAncillaryData, bridgePool.options.address) - .call(); - await mockOracle.methods - .pushPrice(defaultIdentifier, proposalEvent.returnValues.timestamp, stampedDisputeAncillaryData, price) - .send({ from: owner }); - const disputeEvent = (await optimisticOracle.getPastEvents("DisputePrice", { fromBlock: 0 }))[0]; - await optimisticOracle.methods - .settle( - bridgePool.options.address, - defaultIdentifier, - disputeEvent.returnValues.timestamp, - disputeEvent.returnValues.ancillaryData, - disputeEvent.returnValues.request - ) - .send({ from: accounts[0] }); - - // Resolve re-relay. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - // Should not be able to settle relay again. - assert( - await didContractThrow( - bridgePool.methods - .settleRelay(defaultDepositData, { ...relayAttemptData, relayState: InsuredBridgeRelayStateEnum.FINALIZED }) - .send({ from: rando }) - ) - ); - - assert.equal( - (await l1Token.methods.balanceOf(rando).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Follow-up relayer should receive proposal bond + slow relay reward" - ); - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - totalDisputeRefund.toString(), - "Original relayer should receive entire bond back + 1/2 of loser's bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity).sub( - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount) - ), - "Pool should have initial liquidity amount minus relay amount sub LP fee" - ); - assert.equal( - (await l1Token.methods.balanceOf(l1Recipient).call()).toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString(), - "Recipient should receive total relay amount + instant relay fee" - ); - }); - it("Instant relayer address persists for subsequent relays when a pending relay is disputed", async () => { - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Speed up pending relay. - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // Dispute bond should be equal to proposal bond, and OptimisticOracle needs to be able to pull dispute bond - // from disputer. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - - // Mint another relayer a bond to relay again and check that the instant relayer address is migrated: - // Advance time so that price request is different. - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - // Cache price request timestamp. - await advanceTime(1); - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - relayAttemptData = { - ...relayAttemptData, - slowRelayer: rando, - priceRequestTime: requestTimestamp, - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - - // Check that the instant relayer address persists for the original relay params. - const newRelayStatus = await bridgePool.methods.relays(defaultDepositHash).call(); - assert.equal(newRelayStatus, generateRelayHash(relayAttemptData)); - const instantRelayHash = generateInstantRelayHash(defaultDepositHash, relayAttemptData); - assert.equal(await bridgePool.methods.instantRelays(instantRelayHash).call(), instantRelayer); - }); - it("Optimistic Oracle rejects proposal due to final fee change", async () => { - // Standard setup. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // Relay should be canceled and all parties should be refunded since the final fee increased and the initial bond - // was not sufficient. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await store.methods - .setFinalFee(l1Token.options.address, { rawValue: toBN(finalFee).addn(1).toString() }) - .send({ from: owner }); - const txn = await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - await assertEventEmitted(txn, bridgePool, "RelayCanceled"); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: finalFee }).send({ from: owner }); - assert.equal(await l1Token.methods.balanceOf(relayer).call(), totalRelayBond); - assert.equal(await l1Token.methods.balanceOf(disputer).call(), totalRelayBond); - - // Another relay can be sent. - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - }); - it("Refund on decreased final fee", async () => { - // Standard setup. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - let relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // The final fee is decreased, so the disputer should pay in less and the relayer should be refunded. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await store.methods - .setFinalFee(l1Token.options.address, { rawValue: toBN(finalFee).subn(1).toString() }) - .send({ from: owner }); - const txn = await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - await assertEventEmitted(txn, bridgePool, "RelayDisputed"); - await assertEventEmitted(txn, optimisticOracle, "RequestPrice"); - await assertEventEmitted(txn, optimisticOracle, "ProposePrice"); - await assertEventEmitted(txn, optimisticOracle, "DisputePrice"); - assert.equal(await l1Token.methods.balanceOf(relayer).call(), "1"); - assert.equal(await l1Token.methods.balanceOf(disputer).call(), "1"); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: finalFee }).send({ from: owner }); - - // Another relay can be sent. - await l1Token.methods.mint(rando, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: rando }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: rando }); - }); - }); - describe("Settle finalized relay", () => { - beforeEach(async function () { - // Add liquidity to the pool - await l1Token.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - }); - it("Cannot settle successful disputes", async () => { - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Dispute bond should be equal to proposal bond, and OptimisticOracle needs to be able to pull dispute bond - // from disputer. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData).send({ from: disputer }); - const proposalEvent = (await optimisticOracle.getPastEvents("ProposePrice", { fromBlock: 0 }))[0]; - - // Can resolve OO request. - const price = toWei("0"); - const expectedRelayAncillaryData = await bridgePool.methods - .getRelayAncillaryData(defaultDepositData, relayAttemptData) - .call(); - const stampedDisputeAncillaryData = await optimisticOracle.methods - .stampAncillaryData(expectedRelayAncillaryData, bridgePool.options.address) - .call(); - await mockOracle.methods - .pushPrice(defaultIdentifier, proposalEvent.returnValues.timestamp, stampedDisputeAncillaryData, price) - .send({ from: owner }); - const disputeEvent = (await optimisticOracle.getPastEvents("DisputePrice", { fromBlock: 0 }))[0]; - assert.ok( - await optimisticOracle.methods - .settle( - bridgePool.options.address, - defaultIdentifier, - disputeEvent.returnValues.timestamp, - disputeEvent.returnValues.ancillaryData, - disputeEvent.returnValues.request - ) - .call({ from: relayer }) - ); - - // The dispute callback should delete the relay hash making it impossible to settle until a follow-up relay - // is submitted. - assert( - await didContractThrow( - bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }) - ) - ); - - // Resolve OO request. - await optimisticOracle.methods - .settle( - bridgePool.options.address, - defaultIdentifier, - disputeEvent.returnValues.timestamp, - disputeEvent.returnValues.ancillaryData, - disputeEvent.returnValues.request - ) - .send({ from: relayer }); - - // Still cannot settle relay even with correct request params because the relay was disputed. - assert( - await didContractThrow( - bridgePool.methods - .settleRelay(defaultDepositData, { ...relayAttemptData, relayState: InsuredBridgeRelayStateEnum.FINALIZED }) - .send({ from: relayer }) - ) - ); - - // Check payouts since we directly settled with the OO. Disputer should receive full dispute bond back + - // portion of loser's bond. - assert.equal( - (await l1Token.methods.balanceOf(disputer).call()).toString(), - totalDisputeRefund.toString(), - "Disputer should receive entire bond back + 1/2 of loser's bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - "0", - "OptimisticOracle should refund and reward winner of dispute" - ); - assert.equal( - (await l1Token.methods.balanceOf(store.options.address).call()).toString(), - disputePaidToStore.toString(), - "OptimisticOracle should pay store the remaining burned bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - initialPoolLiquidity, - "Pool should have initial liquidity amount" - ); - }); - it("Can settle pending relays that passed challenge period directly via BridgePool", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Cannot settle if there is no price available - const settleTxn = bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData); - assert(await didContractThrow(settleTxn.send({ from: relayer }))); - - // Can optionally speed up pending relay. - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - assert.ok( - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: instantRelayer }) - ); - - // Set time such that optimistic price request is settleable. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - // Cannot speed up relay now that contract time has passed relay expiry. - assert( - await didContractThrow( - bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: instantRelayer }) - ) - ); - - // Can now settle relay since OO resolved request. - assert.ok(await settleTxn.send({ from: relayer })); - await assertEventEmitted(settleTxn, bridgePool, "RelaySettled", (ev) => { - return ( - ev.depositHash === defaultDepositHash && - ev.caller === relayer && - ev.relay.slowRelayer === relayer && - ev.relay.relayId === relayAttemptData.relayId.toString() && - ev.relay.realizedLpFeePct === relayAttemptData.realizedLpFeePct && - ev.relay.priceRequestTime === relayAttemptData.priceRequestTime && - ev.relay.relayState === relayAttemptData.relayState - ); - }); - - // Cannot re-settle. - assert( - await didContractThrow( - bridgePool.methods - .settleRelay(defaultDepositData, { ...relayAttemptData, relayState: InsuredBridgeRelayStateEnum.FINALIZED }) - .send({ from: relayer }) - ) - ); - - // Check token balances. - // -Slow relayer should get back their proposal bond + reward from BridgePool. - assert.equal( - (await l1Token.methods.balanceOf(relayer).call()).toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Relayer should receive proposal bond + slow relay reward" - ); - // - Optimistic oracle should have no funds (it was not involved). - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - "0", - "OptimisticOracle should refund proposal bond" - ); - - // - Bridge pool should have the amount original pool liquidity minus the amount sent to l1Recipient and amount - // sent to slow relayer. This is equivalent to the initial pool liquidity - the relay amount + realized LP fee. - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity).sub(toBN(relayAmount)).add(realizedLpFeeAmount).toString(), - "BridgePool should have balance reduced by relay amount less slow fees and rewards" - ); - - // - Recipient should receive the bridged amount minus the slow relay fee and the LP fee. - assert.equal( - (await l1Token.methods.balanceOf(l1Recipient).call()).toString(), - slowRelayAmountSubFee, - "Recipient should have bridged amount minus fees" - ); - }); - it("Instant and slow relayers should get appropriate rewards, pool reimburse the instant relayer", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Speed up relay. - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // Set time such that optimistic price request is settleable. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - const relayerBalanceBefore = await l1Token.methods.balanceOf(relayer).call(); - const instantRelayerBalanceBefore = await l1Token.methods.balanceOf(instantRelayer).call(); - - // Settle relay. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - - // Check token balances. - // - Slow relayer should get back their proposal bond from OO and reward from BridgePool. - // - Fast relayer should get reward from BridgePool and the relayed amount, minus LP and slow relay fee. This - // is equivalent to what the l1Recipient received + the instant relayer fee. - assert.equal( - toBN(await l1Token.methods.balanceOf(relayer).call()) - .sub(toBN(relayerBalanceBefore)) - .toString(), - toBN(totalRelayBond).add(realizedSlowRelayFeeAmount).toString(), - "Slow relayer should receive proposal bond + slow relay reward" - ); - assert.equal( - toBN(await l1Token.methods.balanceOf(instantRelayer).call()) - .sub(toBN(instantRelayerBalanceBefore)) - .toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString(), - "Instant relayer should receive instant relay reward + the instant relay amount sub fees" - ); - assert.equal( - (await l1Token.methods.balanceOf(optimisticOracle.options.address).call()).toString(), - "0", - "OptimisticOracle should refund proposal bond" - ); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity) - .sub(toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).add(realizedSlowRelayFeeAmount)) - .toString(), - "BridgePool should have balance reduced by relayed amount to l1Recipient" - ); - }); - it("Cannot settle another unique relay request by passing an old request", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const relayId = (await bridgePool.methods.numberOfRelays().call()).toString(); - - // Send two relays with the same timestamp. This will produce two price requests with the same requester and - // timestamp. The ancillary data will differ because of the relay nonce. - const relayAttemptData1 = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - relayId: relayId, - }; - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Dispute the first relay to make room for the second. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: disputer }); - await bridgePool.methods.disputeRelay(defaultDepositData, relayAttemptData1).send({ from: disputer }); - await l1Token.methods.mint(relayer, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Resolve the dispute with the price of 1. - const price = toWei("1"); - const expectedRelayAncillaryData = await bridgePool.methods - .getRelayAncillaryData(defaultDepositData, relayAttemptData1) - .call(); - const stampedDisputeAncillaryData = await optimisticOracle.methods - .stampAncillaryData(expectedRelayAncillaryData, bridgePool.options.address) - .call(); - await mockOracle.methods - .pushPrice(defaultIdentifier, requestTimestamp, stampedDisputeAncillaryData, price) - .send({ from: owner }); - const disputeEvent = (await optimisticOracle.getPastEvents("DisputePrice", { fromBlock: 0 }))[0]; - await optimisticOracle.methods - .settle( - bridgePool.options.address, - defaultIdentifier, - disputeEvent.returnValues.timestamp, - disputeEvent.returnValues.ancillaryData, - disputeEvent.returnValues.request - ) - .send({ from: relayer }); - - // We should not be able to settle the second relay attempt by passing in price request information from the - // newly resolved first relay attempt. The contract should check that the price request information. - assert( - await didContractThrow( - bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData1).send({ from: relayer }) - ) - ); - }); - it("Cannot settle someone else's relay until 15 minutes delay has passed", async () => { - // Cache price request timestamp. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - // Proposer approves pool to withdraw total bond. - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Speed up relay. - await l1Token.methods.approve(bridgePool.options.address, slowRelayAmountSubFee).send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - // Set time such that optimistic price request is settleable. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - const relayerBalanceBefore = await l1Token.methods.balanceOf(relayer).call(); - const randoBalanceBefore = await l1Token.methods.balanceOf(rando).call(); - - // Settle relay. - assert( - await didContractThrow( - bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }) - ) - ); - - await timer.methods.setCurrentTime(Number(expectedExpirationTimestamp) + 901).send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - - assert.equal( - toBN(await l1Token.methods.balanceOf(relayer).call()) - .sub(toBN(relayerBalanceBefore)) - .toString(), - toBN(totalRelayBond).toString(), - "Slow relayer should only receive proposal bond" - ); - - assert.equal( - toBN(await l1Token.methods.balanceOf(rando).call()) - .sub(toBN(randoBalanceBefore)) - .toString(), - toBN(realizedSlowRelayFeeAmount).toString(), - "Settler should receive relay fee" - ); - }); - }); - describe("Liquidity provision", () => { - beforeEach(async function () { - await l1Token.methods.mint(rando, toWei("100")).send({ from: owner }); - }); - it("Deposit liquidity", async () => { - // Before adding liquidity pool should have none and LP should have no LP tokens. - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), "0"); - assert.equal((await lpToken.methods.balanceOf(rando).call()).toString(), "0"); - - // Starting exchange rate should be 1, even though there is no liquidity in the pool. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Approve funds and add to liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: rando }); - await bridgePool.methods.addLiquidity(toWei("10")).send({ from: rando }); - - // Check balances have updated accordingly. Check liquid reserves incremented. Check exchange rate unchanged. - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("10")); - assert.equal((await lpToken.methods.balanceOf(rando).call()).toString(), toWei("10")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("10")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - }); - it("Prevent ETH sent to non WETH pool deposits", async () => { - // If the user tries to deposit non-erc20 token with msg.value included in their tx, should revert. - assert.isFalse(await bridgePool.methods.isWethPool().call()); - - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: rando }); - - assert( - await didContractThrow( - bridgePool.methods - .addLiquidity(initialPoolLiquidity) - .send({ from: liquidityProvider, value: toBN(initialPoolLiquidity) }) - ) - ); - assert( - await didContractThrow( - bridgePool.methods - .addLiquidity(initialPoolLiquidity) - .send({ from: liquidityProvider, value: toBN(initialPoolLiquidity).subn(10) }) - ) - ); - }); - it("Withdraw liquidity", async () => { - // Approve funds and add to liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: rando }); - await bridgePool.methods.addLiquidity(toWei("10")).send({ from: rando }); - - // LP redeems half their liquidity. Balance should change accordingly. - await bridgePool.methods.removeLiquidity(toWei("5"), false).send({ from: rando }); - - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("5")); - assert.equal((await lpToken.methods.balanceOf(rando).call()).toString(), toWei("5")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("5")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // LP redeems their remaining liquidity. Balance should change accordingly. - await bridgePool.methods.removeLiquidity(toWei("5"), false).send({ from: rando }); - - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("0")); - assert.equal((await lpToken.methods.balanceOf(rando).call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - }); - it("Prevent ETH removal from non WETH pool", async () => { - // If the user tries to withdraw ETH from a non-eth pool(standard ERC20 pool) it should revert. - // Approve funds and add to liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: rando }); - await bridgePool.methods.addLiquidity(toWei("10")).send({ from: rando }); - - assert( - await didContractThrow(bridgePool.methods.removeLiquidity(toWei("5"), true).send({ from: liquidityProvider })) - ); - }); - it("Withdraw liquidity is blocked when utilization is too high", async () => { - // Approve funds and add to liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Initiate a relay. The relay amount is 10% of the total pool liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: relayer }); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - assert.equal(await bridgePool.methods.pendingReserves().call(), defaultDepositData.amount); - - // If the LP tries to pull out anything more than 90% of the pool funds this should revert. The 10% of the funds - // are allocated for the deposit and the contract should prevent any additional amount over this from being removed. - assert( - await didContractThrow( - bridgePool.methods - .removeLiquidity( - toBN(initialPoolLiquidity) - .mul(toBN(toWei("0.90"))) // try withdrawing 95%. should fail - .div(toBN(toWei("1"))) - .addn(1) // 90% + 1 we - .toString(), - false - ) - .send({ from: liquidityProvider }) - ) - ); - - // Should be able to withdraw 90% exactly, leaving the exact amount of liquidity in the pool to finalize the relay. - await bridgePool.methods - .removeLiquidity( - toBN(initialPoolLiquidity) - .mul(toBN(toWei("0.9"))) // try withdrawing 95%. should fail - .div(toBN(toWei("1"))) - .toString(), - false - ) - .send({ from: liquidityProvider }); - - // As we've removed all free liquidity the liquid reserves should now be zero. - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); - - // Next, finalize the bridging action. This should reset the pendingReserves to 0 and increment the utilizedReserves. - // Expire and settle proposal on the OptimisticOracle. - await timer.methods - .setCurrentTime(Number(await bridgePool.methods.getCurrentTime().call()) + defaultLiveness) - .send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - // Check the balances have updated correctly. Pending should be 0 (funds are actually utilized now), liquid should - // be equal to the LP fee of 10 and the utilized should equal the total bridged amount (100). - assert.equal(await bridgePool.methods.pendingReserves().call(), "0"); - assert.equal(await bridgePool.methods.liquidReserves().call(), toWei("10")); - assert.equal(await bridgePool.methods.utilizedReserves().call(), defaultDepositData.amount); - - // The LP should be able to withdraw exactly 10 tokens (they still have 100 tokens in the contract). Anything - // more than this is not possible as the remaining funds are in transit from L2. - assert( - await didContractThrow( - bridgePool.methods - .removeLiquidity(toBN(toWei("10")).addn(1).toString(), false) - .send({ from: liquidityProvider }) - ) - ); - await bridgePool.methods.removeLiquidity(toWei("10"), false).send({ from: liquidityProvider }); - - assert.equal(await bridgePool.methods.pendingReserves().call(), "0"); - assert.equal(await bridgePool.methods.liquidReserves().call(), "0"); - assert.equal(await bridgePool.methods.utilizedReserves().call(), defaultDepositData.amount); - }); - it("Can add liquidity multiple times", async () => { - // Approve funds and add to liquidity. The first addLiquidity always succeeds because `totalSupply = 0` so - // exchangeRateCurrent() always returns 1e18. In this test we test that subsequent calls to exchangeRateCurrent() - // from addLiquidity modify state as expected. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Initiate a relay. The relay amount is 10% of the total pool liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: relayer }); - - // Utilized reserves are 0 before any relays. Added liquidity is available as liquid reserves. - assert.equal(await bridgePool.methods.utilizedReserves().call(), "0"); - assert.equal(await bridgePool.methods.liquidReserves().call(), initialPoolLiquidity); - - // Add more liquidity: - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Liquid reserves captures the two added liquidity transfers. - assert.equal(await bridgePool.methods.utilizedReserves().call(), "0"); - assert.equal(await bridgePool.methods.liquidReserves().call(), toBN(initialPoolLiquidity).mul(toBN("2"))); - - // The above equation would fail if `addLiquidity()` transferred tokens to the contract before updating internal - // state via `sync()`, which checks the contract's balance and uses the number to update liquid + utilized - // reserves. If the contract's balance is higher than expected, then this state can be incorrect. - }); - it("Can call remove liquidity when internal accounting mismatches state, requiring sync", async () => { - // Approve funds and add to liquidity. - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: rando }); - - await bridgePool.methods.addLiquidity(toWei("10")).send({ from: rando }); - // pool should have exactly 10 tokens worth in it. - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("10")); - - // utilize half the liquidity. - await l1Token.methods.mint(relayer, toWei("10")).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: relayer }); - await bridgePool.methods.relayDeposit(...generateRelayParams({ amount: toWei("5") })).send({ from: relayer }); - - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - proposerBond: toWei("0.25"), - }; - // Expire and settle proposal on the OptimisticOracle. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - await bridgePool.methods - .settleRelay({ ...defaultDepositData, amount: toWei("5") }, relayAttemptData) - .send({ from: relayer }); - - // Token balance (and liquid reserves) should be 10 - 5 + 5 * 0.1 = 5.5 (5 * 0.1 is the realized LP fee fee). - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("5.5")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("5.5")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("5")); - - // If the LP tries to pull out more than the liquid reserves, should revert. - assert(await didContractThrow(bridgePool.methods.removeLiquidity(toWei("6"), false).send({ from: rando }))); - - // Now, consider some funds have come over the canonical bridge that enable the withdrawal attempt. - await l1Token.methods.mint(bridgePool.options.address, toWei("1")).send({ from: owner }); - - // The "true" liquid reserves should now be 6.5. However, sync has not been called so the contracts state does not - // match the actual token balances. - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("6.5")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("5.5")); - - // Despite this, the user should be able to withdraw their liquidity as the method should internally calls _sync - await bridgePool.methods.removeLiquidity(toWei("6"), false).send({ from: rando }); - }); - }); - describe("Virtual balance accounting", () => { - beforeEach(async function () { - // For the next few tests, add liquidity, relay and finalize the relay as the initial state. - - // Approve funds and add to liquidity. - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Mint relayer bond. - await l1Token.methods.mint(relayer, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Expire and settle proposal on the OptimisticOracle. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - }); - it("SettleRelay modifies virtual balances", async () => { - // Exchange rate should still be 1, no fees accumulated yet as no time has passed from the settlement. Pool - // balance, liquidReserves and utilizedReserves should update accordingly. Calculate the bridge tokens used as - // the amount sent to the receiver+the bond. - const bridgeTokensUsed = toBN(slowRelayAmountSubFee).add(realizedSlowRelayFeeAmount); - - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - assert.equal( - (await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(initialPoolLiquidity).sub(bridgeTokensUsed).toString() - ); - assert.equal((await lpToken.methods.balanceOf(liquidityProvider).call()).toString(), initialPoolLiquidity); - assert.equal( - (await bridgePool.methods.liquidReserves().call()).toString(), - toBN(initialPoolLiquidity).sub(bridgeTokensUsed).toString() - ); - assert.equal( - (await bridgePool.methods.utilizedReserves().call()).toString(), - bridgeTokensUsed.add(realizedLpFeeAmount).toString() - ); - }); - it("Fees correctly accumulate to LPs over the one week loan interval", async () => { - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - - // As no time has elapsed, no fees should be earned and the exchange rate should still be 1. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Advance time by a 2 days (172800s) and check that the exchange rate increments accordingly. From the bridging - // action 10 L1 tokens have been allocated for fees (defaultRealizedLpFee and the relayAmount is 100). At a rate - // of lpFeeRatePerSecond set to 0.0000015, the expected fee allocation should be rate_per_second * - // seconds_since_last_action* undistributed_fees: 0.0000015*172800*10=2.592. Expected exchange rate is - // (liquidReserves+utilizedReserves+cumulativeFeesUnbridged-undistributedLpFees)/totalLpTokenSupply - // =(910+90+10-7.408)/1000=1.002592. - await advanceTime(172800); - - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // If we now call exchangeRateCurrent the fee state variables should increment to the expected size. - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - // undistributedLpFees should be the total fees (10) minus the cumulative fees. - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("7.408")); // 10-2.592 - // Last LP fee update should be the current time. - assert.equal( - await (await bridgePool.methods.lastLpFeeUpdate().call()).toString(), - (await timer.methods.getCurrentTime().call()).toString() - ); - - // Next, To simulate finalization of L2->L1 token transfers due to bridging (via the canonical bridge) send tokens - // directly to the bridgePool. Note that on L1 this is exactly how this will happen as the finalization of bridging - // occurs without informing the l1Recipient (tokens are just "sent"). Validate the sync method updates correctly. - // Also increment time such that we are past the 1 week liveness from OO. increment by 5 days (432000s). - await advanceTime(432000); - - // At this point it is useful to verify that the exchange rate right before the tokens hit the contract is equal - // to the rate right after they hit the contract, without incrementing any time. This proves the exchangeRate - // equation has no discontinuity in it and that fees are correctly captured when syncing the pool balance. - // accumulated fees should be updated accordingly. a total of 1 week has passed at a rate of 0.0000015. Total - // expected cumulative LP fees earned of 2.592+7.408* 4 32000*0.0000015=7.392384 - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - - // expected exchange rate of (910+90+10-(10-7.392384))/1000=1.007392384 - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.007392384")); - - // Now, we can mint the tokens to the bridge pool. When we call exchangeRateCurrent again it will first sync the - // token balances, thereby updating the variables to: • liquidReserves=1010 (initial liquidity+LP fees) - // • utilizedReserves=0 (-10 from sync +10 from LP fees)• cumulativeFeesUnbridged =0 (all fees moved over). - // • undistributedLpFees=10-7.392384=2.607616 (unchanged from before as no time has elapsed) - // overall expected exchange rate should be the SAME as (1010+0+0-(10-7.392384))/1000 - await l1Token.methods.mint(bridgePool.options.address, relayAmount).send({ from: owner }); - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.007392384")); - // We can check all other variables to validate the match to the above comment. - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("1010")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("1010")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), "0"); - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("2.607616")); - }); - it("Fees correctly accumulate with multiple relays", async () => { - // Advance time by a 2 days (172800s) and check that the exchange rate increments accordingly. Expected exchange rate is (910+90+10-(10-0.0000015*172800*10))/1000=1.002592. - await advanceTime(172800); - - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // Next, bridge another relay. This time for 50 L1 tokens. Keep the fee percentages at the same size: 10% LP fee - // 1% slow and 1% instant relay fees. Update the existing data structures to simplify the process. - const depositData = { - ...defaultDepositData, - depositId: defaultDepositData.depositId + 1, - amount: toWei("50"), - l1Recipient: rando, - quoteTimestamp: Number(defaultDepositData.quoteTimestamp) + 172800, - }; - - await l1Token.methods.mint(relayer, totalRelayBond).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - const newRelayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayId: (await bridgePool.methods.numberOfRelays().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - proposerBond: toWei("2.5"), - }; - await bridgePool.methods.relayDeposit(...generateRelayParams(depositData)).send({ from: relayer }); - - // Expire and settle proposal on the OptimisticOracle. - await advanceTime(defaultLiveness); - - // Settle the relay action. - await bridgePool.methods.settleRelay(depositData, newRelayAttemptData).send({ from: relayer }); - - // Double check the rando balance incremented by the expected amount of: 50-5 (LP fee)-0.5 (slow relay fee). - assert.equal((await l1Token.methods.balanceOf(rando).call()).toString(), toWei("44.5")); - - // The exchanger rate should have been increased by the initial deposits fees grown over defaultLiveness. As the - // second deposit happened after this time increment, no new fees should be accumulated at this point for these - // funds. Expected exchange rate is: (865.5+134.5+15-(15-0.0000015*(172800+100)*10+0))/1000=1.0025935 - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.0025935")); - - // Advance time by another 2 days and check that rate updates accordingly to capture the generated. - await advanceTime(172800); - - // Expected exchange rate at this point is easiest to reason about if we consider the two deposits separately. - // Deposit1 was 10 tokens of fees, grown over 4 days+100 seconds. Deposit2 was 5 tokens fees, grown over 2 days. - // Deposit1's first period was 0.0000015*(172800+100)*10=2.5935. This leaves 10-2.5935=7.4065 remaining. - // Deposit1's second period is therefore 0.0000015*(172800)*7.4065=1.9197648. Total accumulated for Deposit1 - // =2.5935+1.9197648=4.5132648. Deposit2 is just 0.0000015*(172800)*5=1.296. Total accumulated fees for - // both deposits is therefore 4.5132648+1.296=5.8092648. We can compute the expected exchange rate using a - // simplified form of (total LP Deposits+accumulated fees)/totalSupply (this form is equivalent to the contracts - // but is easier to reason about as it ignores any bridging actions) giving us (1000+5.8092648)/1000=1.0058092648 - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.0058092648")); - - // Next, advance time past the finalization of the first deposits L2->L1 bridge time by plus 3 days (259200s). - await advanceTime(259200); - await l1Token.methods.mint(bridgePool.options.address, relayAmount).send({ from: owner }); - - // Expected exchange rate can be calculated with the same modified format as before Deposit1's second period is - // now 0.0000015*(172800+259200)*7.4065=4.799412 and Deposit2 is 0.0000015*(172800+259200)*5=3.24. Cumulative fees - // are 2.5935+4.799412+3.24=10.632912 Therefore expected exchange rate is (1000+10.632912)/1000=1.010632912 - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.010632912")); - - // balance is 1000 initial liquidity - 90 bridged for Deposit1, -45 bridged for Deposit2 +100 for - // finalization of Deposit1=1000-90-45+100=965 - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("965")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("965")); - // utilizedReserves is 90 for Deposit1 45 for Deposit2 -100 for finalization of Deposit1. utilizedReserves also - // contains the implicit LP fees so that's 10 for Deposit1 and 5 for Deposit2=90+45-100+10+5=50 - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("50")); - // undistributedLpFees is 10+5 for the deposits - 10.632912 (the total paid thus far)=10+5-10.632912=4.367088 - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("4.367088")); - - // Advance time another 2 days and finalize the Deposit2. - // Next, advance time past the finalization of the first deposits L2->L1 bridge time by plus 3 days (259200s). - await advanceTime(172800); - await l1Token.methods.mint(bridgePool.options.address, depositData.amount).send({ from: owner }); - - // Expected exchange rate is now Deposit1's second period is now 0.0000015*(172800)*(7.4065-4.799412)=0.6757572096 - // and Deposit2 is 0.0000015*(172800)*(5-3.24)=0.456192. Cumulative fees are 10.632912+0.6757572096+0.456192= - // 11.7648612096 Therefore expected exchange rate is (1000+11.7648612096)/1000=1.0117648612096 - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.0117648612096")); - // Token balance should be 1000 initial liquidity - 90 bridged for Deposit1, -45 bridged for Deposit2+100 for - // finalization of Deposit1+50 for finalization of Deposit2 = 1000-90-45+100+50=965. This is equivalent to the - // original liquidity+the total LP fees (1000+10+5). - assert.equal((await l1Token.methods.balanceOf(bridgePool.options.address).call()).toString(), toWei("1015")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("1015")); - // utilizedReserves is 90 for Deposit1 45 for Deposit2 -100 -50 for finalization of deposits=90+45-100-50=-15 - // assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("-15")); - // undistributedLpFees is 10+5 for the deposits - 11.7648612096 (the total paid thus far)=10+5-11.7648612096=3.2351387904 - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("3.2351387904")); - - // Finally, advance time by a lot into the future (100 days). All remaining fees should be paid out. Exchange rate - // should simply be (1000+10+5)/1000=1.015 for the culmination of the remaining fees. - await advanceTime(8640000); - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.015")); - // All fees have been distributed. 0 remaining fees. - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("0")); - }); - it("Adding/removing impact exchange rate accumulation as expected", async () => { - // Advance time by a 2 days (172800s). Exchange rate should be (1000+10*172800*0.0000015)/1000=1.002592 - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // Now, remove the equivalent of 100 units of LP Tokens. This method takes in the number of LP tokens so we will - // end up withdrawing slightly more than the number of LP tokens as the number of LP tokens * the exchange rate. - await bridgePool.methods.removeLiquidity(toWei("100"), false).send({ from: liquidityProvider }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // The internal counts should have updated as expected. - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("809.7408")); // 1000-90-100*1.002592 - // utilizedReserves have 90 for the bridging action and 10 for the pending LP fees[unchanged] - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("100")); - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("7.408")); // 10-10*172800*0.0000015 - - // Advance time by 2 days to accumulate more fees. As a result of that decrease number of liquid+utilized reserves, - // the exchange rate should increment faster. Exchange rate is calculated using the equation ExchangeRate := - // (liquidReserves+utilizedReserves+cumulativeLpFeesEarned-undistributedLpFees)/lpTokenSupply. undistributedLpFees - // is found as the total fees to earn, minus the sum of period1's fees earned and the second period's fees. The - // first period is 10*172800*0.0000015=2.592. The second period is the remaining after the first, with fees applied - // as (10-2.592)*172800*0.0000015=1.9201536. Total fees paid are 2.592+1.9201536=4.5121536. Therefore, undistributedLpFees= 10-4.5121536=5.4878464. Exchange rate is therefore (809.7408+90+10-5.4878464)/900=1.004725504 - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.004725504")); - - // Next, advance time past the conclusion of this deposit (+5 days) and mint tokens to the contract. - await advanceTime(432000); - await l1Token.methods.mint(bridgePool.options.address, toWei("100")).send({ from: owner }); - - // Recalculate the exchange rate. As we did not force sync at the previous step we can just extent the period2 - // accumulated fees as (10-2.592)*(172800+432000)*0.0000015=6.7205376. Cumulative fees are therefore - // 2.592+6.7205376=9.3125376. Hence, undistributedLpFees=10-9.3125376=0.6874624. Exchange rate is thus - // (809.7408+90+10-0.6874624)/900=1.010059264 - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.010059264")); - - // Finally, increment time such that the remaining fees are attributed (say 100 days). Expected exchange rate is - // simply the previous equation with the undistributedLpFees set to 0. Exchange rate will be - // (809.7408+90+10-0)/900=1.010823111111111111 - await advanceTime(8640000); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.010823111111111111")); - }); - it("Fees & Exchange rate can correctly handle gifted tokens", async () => { - // We cant control when funds are sent to the contract, just like we cant control when the bridging action - // concludes. If someone was to randomly send the contract tokens the exchange rate should ignore this. The - // contract should ignore the exchange rate if someone was to randomly send tokens. - - // Advance time by a 2 days (172800s). Exchange rate should be (1000+10*172800*0.0000015)/1000=1.002592 - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // Now, randomly send the contract 10 tokens. This is not part of the conclusion of any bridging action. The - // exchange rate should not be modified at all. - await l1Token.methods.mint(bridgePool.options.address, toWei("10")).send({ from: owner }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - - // However, the internal counts should have updated as expected. - await bridgePool.methods.exchangeRateCurrent().send({ from: rando }); // force sync - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("920")); // 1000-90+10 - // utilizedReserves are 90 for the bridging action, -10 for the tokens sent to the bridge +10 for the pending LP fees. - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("90")); // 90-10+10 - assert.equal((await bridgePool.methods.undistributedLpFees().call()).toString(), toWei("7.408")); // 10-10*172800*0.0000015 - - // Advance time by 100 days to accumulate the remaining fees. Exchange rate should accumulate accordingly to - // (1000+10)/1000=1.01 - await advanceTime(8640000); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.01")); - - // Sending more tokens does not modify the rate. - await l1Token.methods.mint(bridgePool.options.address, toWei("100")).send({ from: owner }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.01")); - }); - it("Edge cases cant force exchange rate current to revert", async () => { - // There are some extreme edge cases that can cause the exchange rate to revert in pervious versions of the smart - // contracts. This test aims to mimic them and show that there is no condition where this is an issue with the - // modified exchange rate computation logic. - // Create a relay that uses all liquid reserves. - const liquidReservesPreLargeRelay = await bridgePool.methods.liquidReserves().call(); - - await l1Token.methods.mint(relayer, liquidReservesPreLargeRelay).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, liquidReservesPreLargeRelay).send({ from: relayer }); - - let requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - await bridgePool.methods - .relayDeposit(...generateRelayParams({ depositId: 1, amount: liquidReservesPreLargeRelay })) - .send({ from: relayer }); - - console.log( - "bond", - toBN(liquidReservesPreLargeRelay) - .mul(toBN(defaultProposerBondPct)) - .div(toBN(toWei("1"))) - .toString() - ); - - const relayAttemptData2 = { - ...defaultRelayData, - relayId: 1, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - proposerBond: toBN(liquidReservesPreLargeRelay) - .mul(toBN(defaultProposerBondPct)) - .div(toBN(toWei("1"))) - .toString(), - }; - await advanceTime(defaultLiveness); - await bridgePool.methods - .settleRelay({ ...defaultDepositData, depositId: 1, amount: liquidReservesPreLargeRelay }, relayAttemptData2) - .send({ from: relayer }); - - // now, liquid reserves should be the realizedLP Fee from the previous relay. - assert.equal( - (await bridgePool.methods.liquidReserves().call()).toString(), - toBN(liquidReservesPreLargeRelay) - .mul(toBN(defaultRealizedLpFee)) - .div(toBN(toWei("1"))) - .toString() - ); - - await bridgePool.methods.exchangeRateCurrent().call(); // Exchange rate current should still work, as expected (not revert). - - // Further relay the remaining liquid reserves. - requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const liquidReservesPostLargeRelay = await bridgePool.methods.liquidReserves().call(); - await bridgePool.methods - .relayDeposit(...generateRelayParams({ depositId: 2, amount: liquidReservesPostLargeRelay })) - .send({ from: relayer }); - - // Now, finalize this relay. - const relayAttemptData3 = { - ...defaultRelayData, - relayId: 2, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - proposerBond: toBN(liquidReservesPostLargeRelay) - .mul(toBN(defaultProposerBondPct)) - .div(toBN(toWei("1"))) - .toString(), - }; - - await advanceTime(defaultLiveness); - await bridgePool.methods - .settleRelay({ ...defaultDepositData, depositId: 2, amount: liquidReservesPostLargeRelay }, relayAttemptData3) - .send({ from: relayer }); - - // Exchange rate current should still work, as expected (not revert). Note that at this point the undistributed LP fees exceed the liquid reserves. - const endRate = await bridgePool.methods.exchangeRateCurrent().call(); - - // Mimic some funds coming over the canonical bridge by a mint. This should not affect the exchange rate. - await l1Token.methods.mint(bridgePool.options.address, toWei("500")).send({ from: owner }); - assert.equal(endRate.toString(), (await bridgePool.methods.exchangeRateCurrent().call()).toString()); - - // Finally, dump a larger amount of tokens into the pool than was originally added by LPs. Again, this should - // break nothing. - await l1Token.methods.mint(bridgePool.options.address, toWei("1500")).send({ from: owner }); - await bridgePool.methods.exchangeRateCurrent().call(); - assert.equal(endRate.toString(), (await bridgePool.methods.exchangeRateCurrent().call()).toString()); - }); - }); - describe("Liquidity utilization rato", () => { - beforeEach(async function () { - // Approve funds and add to liquidity. - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Mint relayer bond. - await l1Token.methods.mint(relayer, totalRelayBond.muln(10)).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond.muln(10)).send({ from: relayer }); - }); - it("Rate updates as expected in slow relay", async () => { - // liquidityUtilizationRatio := - // (relayedAmount + pendingReserves + utilizedReserves) / (liquidReserves + utilizedReserves) - - // Before any relays (nothing in flight and none finalized) the rate should be 0 (no utilization). - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("1000")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - - // liquidityUtilizationPostRelay of the relay size should correctly factor in the relay. - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(relayAmount).call()).toString(), - toWei("0.1") - ); - - // Next, relay the deposit and check the utilization updates. - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // The relay amount is set to 10% of the pool liquidity. As a result, the post relay utilization should be 10%. - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0.1")); - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("100")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("1000")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - - // Advance time and settle the relay. This will send funds from the bridge pool to the recipient and slow relayer. - // After this action, the utilized reserves should decrease. The conclusion of the bridging action pays the slow - // relayer their reward of 1% and the recipient their bridged amount of 89% of the 100 bridged (100%-1%-10%). - // As a result, the pool should have it's initial balance, minus recipient amount, minus slow relayer reward. - // i.e 1000-1-89=910. The liquidity utilization ratio should therefore be 100 / (910 + 100) = 0.099009900990099009 - - // Settle the relay action. - await advanceTime(defaultLiveness); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - - // Validate the balances and utilized ratio match to the above comment. Utilization % should be slightly less - // than 10% because fees are included in the denominator. - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("910")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("100")); - assert.equal((await l1Token.methods.balanceOf(l1Recipient).call()).toString(), toWei("89")); - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.099009900990099009") - ); - - // Relay again. - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - const newDeposit = { - ...defaultDepositData, - depositId: 1, - quoteTimestamp: await bridgePool.methods.getCurrentTime().call(), - }; - await bridgePool.methods.relayDeposit(newDeposit, relayAttemptData.realizedLpFeePct).send({ from: relayer }); - - // Check new reserve amounts. Utilization should increase, but should be slightly less - // than 20% because fees are included in the denominator. - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.198019801980198019") - ); - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("100")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("910")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("100")); - - // Mimic the finalization of the first bridging action over the canonical bridge by minting tokens to the - // bridgepool. This should bring the bridge pool balance up to 1010 (the initial 1000 + 10% LP reward from - // bridging action). Utilization should go back to same value it was right before the second relay. - await l1Token.methods.mint(bridgePool.options.address, relayAmount).send({ from: owner }); - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.099009900990099009") - ); - await bridgePool.methods.sync().send({ from: owner }); // Sync state to get updated reserve values that we - // can verify. - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("100")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("1010")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - }); - it("Rate updates as expected with multiple relays and instant relay", async () => { - // Before any relays (nothing in flight and none finalized) the rate should be 0 (no utilization). - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0")); - - // Next, relay the deposit and check the utilization updates. - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // The relay amount is set to 10% of the pool liquidity. As a result, the post relay utilization should be 10%. - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0.1")); - - // Send another relay. This time make the amount bigger (150 tokens). The utilization should be (100+150)/1000= 25%. - - // First, check the liquidityUtilizationPostRelay of the relay size returns expected value. - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("150")).call()).toString(), - toWei("0.25") - ); - await bridgePool.methods - .relayDeposit(...generateRelayParams({ depositId: 1, amount: toWei("150") })) - .send({ from: relayer }); - - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0.25")); - - // Speed up the first relay. This should not modify the utilization ratio as no pool funds are used until finalization. - await l1Token.methods - .approve(bridgePool.options.address, instantRelayAmountSubFee) - .send({ from: instantRelayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0.25")); - - // Advance time and settle the first relay. After this, the liquid reserves should be decremented by the bridged - // amount + the LP fees (to 910, same as in the first test). The pending reserves should be 150, from the second - // bridging action. The utilized reserves should be 100 for the funds in flight from the first bridging action. - // The recipient token balance should be the initial amount, - 1% slow relay, - 1% fast relay - 10% lp fee. - // The utilization ratio should, therefore, be: (150+100)/(910+100)=0.247524752475247524 - await advanceTime(defaultLiveness); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("150")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("910")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("100")); - assert.equal((await l1Token.methods.balanceOf(l1Recipient).call()).toString(), toWei("88")); - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.247524752475247524") - ); - - // Mimic the finalization of the first relay by minting tokens to the pool. After this action, the pool - // will gain 100 tokens from the first deposit. At this point the pendingReserves should be 150 (as before), - // liquid reserves are now 1010 and utilized reserves are set to 0. The utilization ratio is: (150+0)/1010+0=0.148514851485148514 - await l1Token.methods.mint(bridgePool.options.address, relayAmount).send({ from: owner }); - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.148514851485148514") - ); - - // Advance time to accumulate more fees. This should not change the utilization rate. - await advanceTime(defaultLiveness); - await bridgePool.methods.exchangeRateCurrent().send({ from: owner }); // enforce fees are accumulated. - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.148514851485148514") - ); - // Finally relay another deposit. This should modify the utilization again to (150+100)/910+100=0.247524752475247524 - await bridgePool.methods.relayDeposit(...generateRelayParams({ depositId: 2 })).send({ from: relayer }); - assert.equal( - (await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), - toWei("0.247524752475247524") - ); - }); - it("Rate updates as expected in edge cases with tokens minted to the pool to force negative utilizedReserves", async () => { - // Start off by redeeming all liquidity tokens to force everything to zero. - await bridgePool.methods.removeLiquidity(toWei("1000"), false).send({ from: liquidityProvider }); - assert.equal((await bridgePool.methods.pendingReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.liquidReserves().call()).toString(), toWei("0")); - assert.equal((await bridgePool.methods.utilizedReserves().call()).toString(), toWei("0")); - - // Utilization should be 1 if utilizedReserves,pendingReserves,relayedAmount are 0. - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("1")); - assert.equal((await bridgePool.methods.liquidityUtilizationPostRelay(toWei("1")).call()).toString(), toWei("1")); - - // Next, mint tokens to the pool without any liquidity added. Utilization should be 0 - await l1Token.methods.mint(bridgePool.options.address, toWei("1000")).send({ from: owner }); - - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0")); - - // Trying to relay 10 should have a utilization of 10/1000=0.01 - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("10")).call()).toString(), - toWei("0.01") - ); - - // Next, add liquidity back but add less than the amount dropped on the contract. - await bridgePool.methods.addLiquidity(toWei("500")).send({ from: liquidityProvider }); - assert.equal((await bridgePool.methods.liquidityUtilizationCurrent().call()).toString(), toWei("0")); - - // Trying to relay 10 should have a utilization of 10/1500=0.006666666667 - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("10")).call()).toString(), - toWei("0.006666666666666666") - ); - // trying to relay 1250 should have a utilization of 1250/1500=0.8333333333 - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("1250")).call()).toString(), - toWei("0.833333333333333333") - ); - - // Add more liquidity finally such that the liquidity added is more than that minted. - await bridgePool.methods.addLiquidity(toWei("1000")).send({ from: liquidityProvider }); - - // Trying to relay 10 should have a utilization of 10/2500=0.004 - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("10")).call()).toString(), - toWei("0.004") - ); - - // If the amount bridged is the full amount of liquidity it should be utilization = 1. - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("2500")).call()).toString(), - toWei("1") - ); - - // A number greater than the maximum should return the expected amount (can be greater than 100%). - assert.equal( - (await bridgePool.methods.liquidityUtilizationPostRelay(toWei("3000")).call()).toString(), - toWei("1.2") - ); - }); - }); - describe("Canonical bridge finalizing before insured bridge settlement edge cases", () => { - beforeEach(async function () { - await l1Token.methods.mint(liquidityProvider, initialPoolLiquidity).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - await l1Token.methods.mint(relayer, totalRelayBond.muln(100)).send({ from: owner }); - await l1Token.methods.approve(bridgePool.options.address, totalRelayBond.muln(100)).send({ from: relayer }); - }); - it("Exchange rate correctly handles the canonical bridge finalizing before insured relayer begins", async () => { - // Consider the edge case where a user deposits on L2 and no actions happen on L1. This might be due to their - // deposit fees being under priced (not picked up by a relayer). After a week their funds arrive on L1 via the - // canonical bridge. At this point, the transfer is relayed. The exchange rate should correctly deal with this - // without introducing a step in the rate at any point. - - // Advance time by 1 week past the end of the of the L2->L1 liveness period. - await advanceTime(604800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Now, simulate the finalization of of the bridge action by the canonical bridge by minting tokens to the pool. - await l1Token.methods.mint(bridgePool.options.address, toWei("100")).send({ from: owner }); - - // The exchange rate should not have updated. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Only now that the bridging action has concluded through the canonical bridge does a relayer pick up the - // transfer. This could also have been the depositor self relaying. - const requestTimestamp = (await bridgePool.methods.getCurrentTime().call()).toString(); - const expectedExpirationTimestamp = (Number(requestTimestamp) + defaultLiveness).toString(); - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: requestTimestamp, - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // Expire and settle proposal on the OptimisticOracle. - await timer.methods.setCurrentTime(expectedExpirationTimestamp).send({ from: owner }); - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Going forward, the rate should increment as normal, starting from the settlement of the relay. EG advancing time - // by 2 days(172800s) which should increase the rate accordingly (910+90+10-(10-0.0000015*172800*10))/1000=1.002592. - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - }); - it("Exchange rate correctly handles the canonical bridge finalizing before insured relayer finalizes(slow)", async () => { - // Similar to the previous edge case test, consider a case where a user deposit on L2 and the L1 action is only - // half completed (not finalized). This test validates this in the slow case. - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - - // The exchange rate should still be 0 as no funds are actually "used" until the relay concludes. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Advance time by 1 week past the end of the of the L2->L1 liveness period. - await advanceTime(604800); - - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Now, simulate the finalization of of the bridge action by the canonical bridge by minting tokens to the pool. - await l1Token.methods.mint(bridgePool.options.address, toWei("100")).send({ from: owner }); - - // The exchange rate should not have updated. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Only now that the bridging action has concluded do we finalize the relay action. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Going forward, the rate should increment as normal, starting from the settlement of the relay. EG advancing time - // by 2 days(172800s) which should increase the rate accordingly (910+90+10-(10-0.0000015*172800*10))/1000=1.002592. - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - }); - it("Exchange rate correctly handles the canonical bridge finalizing before insured relayer finalizes(instant)", async () => { - // Finally, consider the same case as before except speed up the relay. The behaviour should be the same as the - // previous test (no rate change until the settlement of the relay and ignore tokens sent "early"). - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).call({ from: relayer }); - - // The exchange rate should still be 0 as no funds are actually "used" until the relay concludes. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Advance time by 1 week past the end of the of the L2->L1 liveness period. - await advanceTime(604800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Now, simulate the finalization of of the bridge action by the canonical bridge by minting tokens to the pool. - await l1Token.methods.mint(bridgePool.options.address, toWei("100")).send({ from: owner }); - - // The exchange rate should not have updated. - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Only now that the bridging action has concluded do we finalize the relay action. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: rando }); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1")); - - // Going forward, the rate should increment as normal, starting from the settlement of the relay. EG advancing time - // by 2 days(172800s) which should increase the rate accordingly (910+90+10-(10-0.0000015*172800*10))/1000=1.002592. - await advanceTime(172800); - assert.equal((await bridgePool.methods.exchangeRateCurrent().call()).toString(), toWei("1.002592")); - }); - }); - describe("Weth functionality", () => { - beforeEach(async function () { - // Deploy weth contract - weth = await WETH9.new().send({ from: owner }); - - await collateralWhitelist.methods.addToWhitelist(weth.options.address).send({ from: owner }); - await store.methods.setFinalFee(weth.options.address, { rawValue: finalFee }).send({ from: owner }); - - // Create a new bridge pool, where the L1 Token is weth. - bridgePool = await BridgePool.new( - "Weth LP Token", - "wLPT", - bridgeAdmin.options.address, - weth.options.address, - lpFeeRatePerSecond, - true, // this is a weth pool - timer.options.address - ).send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken( - chainId, - weth.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - 0 - ) - .send({ from: owner }); - - // deposit funds into weth to get tokens to mint. - - await weth.methods.deposit().send({ from: liquidityProvider, value: initialPoolLiquidity }); - await weth.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: liquidityProvider }); - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - - // Mint relayer bond. - await weth.methods.deposit().send({ from: relayer, value: totalRelayBond }); - await weth.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - - await bridgePool.methods.relayDeposit(...generateRelayParams()).send({ from: relayer }); - }); - it("Correctly sends Eth at the conclusion of a slow relay", async () => { - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const recipientEthBalanceBefore = await web3.eth.getBalance(l1Recipient); - await timer.methods - .setCurrentTime( - (Number((await bridgePool.methods.getCurrentTime().call()).toString()) + defaultLiveness).toString() - ) - .send({ from: owner }); - - // Settle request. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - - // Recipient eth balance should increment by the amount withdrawn. - assert.equal( - toBN(await web3.eth.getBalance(l1Recipient)) - .sub(toBN(recipientEthBalanceBefore)) - .toString(), - slowRelayAmountSubFee.toString() - ); - - // Bridge Pool Weth balance should decrement by the amount sent to the recipient. - assert.equal( - toBN(initialPoolLiquidity) - .sub(toBN((await weth.methods.balanceOf(bridgePool.options.address).call()).toString())) - .sub(realizedSlowRelayFeeAmount) - .toString(), - slowRelayAmountSubFee.toString() - ); - }); - it("Correctly sends Eth when speeding up a relay and refunds the instant relayer with Weth at the conclusion of an instant", async () => { - const relayAttemptData = { - ...defaultRelayData, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - await weth.methods.deposit().send({ from: instantRelayer, value: initialPoolLiquidity }); - await weth.methods.approve(bridgePool.options.address, MAX_UINT_VAL).send({ from: instantRelayer }); - - const recipientEthBalanceBefore = await web3.eth.getBalance(l1Recipient); - const instantRelayerWethBalanceBefore = await weth.methods.balanceOf(instantRelayer).call(); - await bridgePool.methods.speedUpRelay(defaultDepositData, relayAttemptData).send({ from: instantRelayer }); - const recipientEthBalanceAfter = await web3.eth.getBalance(l1Recipient); - // Recipient eth balance should increment by the amount withdrawn. - assert.equal( - toBN(recipientEthBalanceAfter).sub(toBN(recipientEthBalanceBefore)).toString(), - instantRelayAmountSubFee.toString() - ); - - const instantRelayerBalancePostSpeedUp = toBN(await weth.methods.balanceOf(instantRelayer).call()); - - // Instant relayer weth balance should decrement by the amount sent to the recipient. - assert.equal( - toBN(instantRelayerWethBalanceBefore).sub(instantRelayerBalancePostSpeedUp).toString(), - instantRelayAmountSubFee.toString() - ); - - // Next, advance time and settle the relay. At this point the instant relayer should be reimbursed the - // instantRelayAmountSubFee + the realizedInstantRelayFeeAmount in Weth. - await timer.methods - .setCurrentTime( - (Number((await bridgePool.methods.getCurrentTime().call()).toString()) + defaultLiveness).toString() - ) - .send({ from: owner }); - - // Settle request. - await bridgePool.methods.settleRelay(defaultDepositData, relayAttemptData).send({ from: relayer }); - - assert.equal( - toBN(await weth.methods.balanceOf(instantRelayer).call()) - .sub(instantRelayerBalancePostSpeedUp) - .toString(), - toBN(instantRelayAmountSubFee).add(realizedInstantRelayFeeAmount).toString() - ); - }); - it("Can handle recipient being a smart contract that does not accept ETH transfer", async () => { - // In the even the recipient is a smart contract that can not accept ETH transfers (no payable receive function) - // and it is a WETH pool, the bridge pool should send WETH ERC20 to the recipient. - - // Relay, setting the finder as the recipient. this is a contract that cant accept ETH. - await weth.methods.deposit().send({ from: relayer, value: totalRelayBond }); - await weth.methods.approve(bridgePool.options.address, totalRelayBond).send({ from: relayer }); - await bridgePool.methods - .relayDeposit(...generateRelayParams({ depositId: 2, l1Recipient: finder.options.address })) - .send({ from: relayer }); - - const relayAttemptData = { - ...defaultRelayData, - l1Recipient: finder.options.address, - priceRequestTime: (await bridgePool.methods.getCurrentTime().call()).toString(), - relayState: InsuredBridgeRelayStateEnum.PENDING, - }; - const recipientEthBalanceBefore = await web3.eth.getBalance(finder.options.address); - const recipientWethBalanceBefore = await weth.methods.balanceOf(finder.options.address).call(); - await timer.methods - .setCurrentTime( - (Number((await bridgePool.methods.getCurrentTime().call()).toString()) + defaultLiveness).toString() - ) - .send({ from: owner }); - - // Settle request. - await bridgePool.methods - .settleRelay( - { ...defaultDepositData, depositId: 2, l1Recipient: finder.options.address }, - { ...relayAttemptData, relayId: 1 } - ) - .send({ from: relayer }); - - // Recipient eth balance should have stayed the same (cant receive eth) - assert.equal( - (await web3.eth.getBalance(finder.options.address)).toString(), - recipientEthBalanceBefore.toString() - ); - - // Recipients WETH balance should have increased instead. - assert.equal( - toBN(await weth.methods.balanceOf(finder.options.address).call()) - .sub(toBN(recipientWethBalanceBefore)) - .toString(), - slowRelayAmountSubFee.toString() - ); - }); - it("LP can send ETH when depositing into a WETH pool", async () => { - // LPs should be able to sent ETH with their deposit when adding funds to a WETH pool. Contract should auto wrap - // the ETH to WETH for them. - - const poolEthBalanceBefore = await web3.eth.getBalance(bridgePool.options.address); - assert.equal(poolEthBalanceBefore, "0"); - const poolWethBalanceBefore = await weth.methods.balanceOf(bridgePool.options.address).call(); - await bridgePool.methods - .addLiquidity(initialPoolLiquidity) - .send({ from: liquidityProvider, value: initialPoolLiquidity }); - - assert.equal(poolEthBalanceBefore, await web3.eth.getBalance(bridgePool.options.address)); - assert.equal( - (await weth.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(poolWethBalanceBefore).add(toBN(initialPoolLiquidity)).toString() - ); - }); - it("Reverts if ETH sent on LP deposit mismatch", async () => { - // If the LP tries to deposit with a msg.value != l1TokenAmount should revert. - assert( - await didContractThrow( - bridgePool.methods - .addLiquidity(initialPoolLiquidity) - .send({ from: liquidityProvider, value: toBN(initialPoolLiquidity).subn(10) }) - ) - ); - }); - it("LP can send WETH to WETH Pool", async () => { - // LPs should be able to sent WETH to the WETH Pool (should act like a normal ERC20 deposit). - - const poolEthBalanceBefore = await web3.eth.getBalance(bridgePool.options.address); - assert.equal(poolEthBalanceBefore, "0"); - const poolWethBalanceBefore = await weth.methods.balanceOf(bridgePool.options.address).call(); - - // Mint some WETH to do the deposit with. - - await weth.methods.deposit().send({ from: liquidityProvider, value: initialPoolLiquidity }); - await weth.methods.approve(bridgePool.options.address, initialPoolLiquidity).send({ from: liquidityProvider }); - - // Value is set to zero. should act like a normal weth deposit. - await bridgePool.methods.addLiquidity(initialPoolLiquidity).send({ from: liquidityProvider }); - assert.equal(poolEthBalanceBefore, await web3.eth.getBalance(bridgePool.options.address)); - assert.equal( - (await weth.methods.balanceOf(bridgePool.options.address).call()).toString(), - toBN(poolWethBalanceBefore).add(toBN(initialPoolLiquidity)).toString() - ); - }); - it("LP can receive ETH when removing liquidity from a WETH pool", async () => { - // LPs should be able to receive eth, if they want, when withdrawing from a WETH pool. - - // Can do a normal ERC20 withdraw from a weth pool. - const userWethBefore = await weth.methods.balanceOf(liquidityProvider).call(); - - await bridgePool.methods.removeLiquidity(toWei("10"), false).send({ from: liquidityProvider }); - - const userWethAfter1 = await weth.methods.balanceOf(liquidityProvider).call(); - - assert.equal( - userWethAfter1, - toBN(userWethBefore) - .add(toBN(toWei("10"))) - .toString() - ); - - // Now try withdrawing into ETH. - const userEthBalanceBefore = await web3.eth.getBalance(liquidityProvider); - - const withdrawTx = await bridgePool.methods.removeLiquidity(toWei("10"), true).send({ from: liquidityProvider }); - - const userWethAfter2 = await weth.methods.balanceOf(liquidityProvider).call(); - - // WETH balance should not have changed after a ETH removal. - assert.equal(userWethAfter1, userWethAfter2); - - // Users eth balance should have increased by the amount withdrawn (10), minus the gas used in the withdrawTx. - const userEthBalanceAfter = await web3.eth.getBalance(liquidityProvider); - assert.equal( - userEthBalanceAfter, - toBN(userEthBalanceBefore).add( - toBN(toWei("10")).sub(toBN(withdrawTx.effectiveGasPrice).mul(toBN(withdrawTx.cumulativeGasUsed))) - ) - ); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/OVM_BridgeDepositBox.js b/packages/core/test/insured-bridge/OVM_BridgeDepositBox.js deleted file mode 100644 index c9907a8add..0000000000 --- a/packages/core/test/insured-bridge/OVM_BridgeDepositBox.js +++ /dev/null @@ -1,279 +0,0 @@ -// This set of tests validates the BridgeDepositBox OVM (Optimism) specific logic such as L1->L2 function calls and the -// use of the OVM token bridge. Bridge Deposit logic is not directly tested. For this see the BridgeDepositBox.js tests. - -const hre = require("hardhat"); -const { web3 } = hre; -const { predeploys } = require("@eth-optimism/contracts"); -const { didContractThrow, ZERO_ADDRESS } = require("@uma/common"); -const { getContract, assertEventEmitted } = hre; - -const { toWei } = web3.utils; - -const { assert } = require("chai"); - -const { deployContractMock } = require("../helpers/SmockitHelper"); - -// Tested contract -const BridgeDepositBox = getContract("OVM_BridgeDepositBox"); - -// Helper contracts -const Token = getContract("ExpandedERC20"); -const Timer = getContract("Timer"); - -// Contract objects -let depositBox, l2CrossDomainMessengerMock, l1TokenAddress, l2Token, timer; - -// As these tests are in the context of l2, we dont have the deployed notion of an "L1 Token". The L1 token is within -// another domain (L1). To represent this, we can generate a random address to represent the L1 token. -l1TokenAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - -const minimumBridgingDelay = 60; // L2->L1 token bridging must wait at least this time. -const depositAmount = toWei("50"); -const slowRelayFeePct = toWei("0.005"); -const instantRelayFeePct = toWei("0.005"); -const quoteTimestampOffset = 60; // 60 seconds into the past. -const chainId = 10; // Optimism mainnet chain ID. - -describe("OVM_BridgeDepositBox", () => { - // Account objects - let accounts, deployer, user1, bridgeAdmin, rando, bridgePool; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [deployer, user1, bridgeAdmin, rando, bridgePool] = accounts; - - timer = await Timer.new().send({ from: deployer }); - }); - - beforeEach(async function () { - // Initialize the cross domain massager messenger mock at the address of the OVM pre-deploy. The OVM will always use - // this address for L1<->L2 messaging. Seed this address with some funds so it can send transactions. - l2CrossDomainMessengerMock = await deployContractMock("L2CrossDomainMessenger", { - address: predeploys.L2CrossDomainMessenger, - }); - await web3.eth.sendTransaction({ from: deployer, to: predeploys.L2CrossDomainMessenger, value: toWei("1") }); - - depositBox = await BridgeDepositBox.new( - bridgeAdmin, - minimumBridgingDelay, - chainId, - ZERO_ADDRESS, // weth address. Weth mode not used in these tests - timer.options.address - ).send({ from: deployer }); - - l2Token = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token.methods.addMember(1, deployer).send({ from: deployer }); - - // Mint tokens to user - await l2Token.methods.mint(user1, toWei("100")).send({ from: deployer }); - }); - describe("Box admin logic", () => { - // Only the crossDomainAdmin, called via the canonical bridge, can: a) change the L1 withdraw contract, - // b) whitelist collateral or c) disable deposits. In production, the crossDomainAdmin will be the Messenger. - // In these tests mock this as being any crossDomainAdmin, calling via the l2MessengerImpersonator. - it("Change L1 admin contract", async () => { - // Owner should start out as the set owner. - assert.equal(await depositBox.methods.crossDomainAdmin().call(), bridgeAdmin); - - // Trying to transfer ownership from non-cross-domain owner should fail. - assert(await didContractThrow(depositBox.methods.setCrossDomainAdmin(user1).send({ from: rando }))); - - // Trying to call correctly via the L2 message impersonator, but from the wrong xDomainMessageSender should revert. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => rando); - - assert( - await didContractThrow( - depositBox.methods.setCrossDomainAdmin(user1).send({ from: predeploys.L2CrossDomainMessenger }) - ) - ); - - // Setting the l2CrossDomainMessengerMock to correctly mock the bridgeAdmin shoZuld let the ownership change. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - - const tx = await depositBox.methods.setCrossDomainAdmin(user1).send({ from: predeploys.L2CrossDomainMessenger }); - - assert.equal(await depositBox.methods.crossDomainAdmin().call(), user1); - - await assertEventEmitted(tx, depositBox, "SetXDomainAdmin", (ev) => { - return ev.newAdmin == user1; - }); - }); - - it("Set minimum bridging delay", async () => { - // Bridging delay should be set initially to the correct value. - assert.equal(await depositBox.methods.minimumBridgingDelay().call(), minimumBridgingDelay); - - // Trying to change bridging delay from non-cross-domain owner should fail. - assert(await didContractThrow(depositBox.methods.setMinimumBridgingDelay(55).send({ from: rando }))); - - // Trying to call correctly via the L2 message impersonator, but from the wrong xDomainMessageSender should revert. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => rando); - - assert( - await didContractThrow( - depositBox.methods.setMinimumBridgingDelay(55).send({ from: predeploys.L2CrossDomainMessenger }) - ) - ); - - // Setting the l2CrossDomainMessengerMock to correctly mock the bridgeAdmin should let the bridging delay change. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - - const tx = await depositBox.methods.setMinimumBridgingDelay(55).send({ from: predeploys.L2CrossDomainMessenger }); - - assert.equal(await depositBox.methods.minimumBridgingDelay().call(), 55); - - await assertEventEmitted(tx, depositBox, "SetMinimumBridgingDelay", (ev) => { - return ev.newMinimumBridgingDelay == 55; - }); - }); - - it("Whitelist collateral", async () => { - // Trying to whitelist tokens from something other than the l2MessengerImpersonator should fail. - assert( - await didContractThrow( - depositBox.methods.whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool).send({ from: rando }) - ) - ); - - // Trying to call correctly via the L2 message impersonator, but from the wrong xDomainMessageSender should revert. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => rando); - - assert( - await didContractThrow( - depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: predeploys.L2CrossDomainMessenger }) - ) - ); - - // Setting the l2CrossDomainMessengerMock to correctly mock the L1WithdrawContract should let the whitelist change. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - - const tx = await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: predeploys.L2CrossDomainMessenger }); - - assert.equal( - (await depositBox.methods.whitelistedTokens(l2Token.options.address).call()).l1Token, - l1TokenAddress - ); - - const expectedLastBridgeTime = await timer.methods.getCurrentTime().call(); - await assertEventEmitted(tx, depositBox, "WhitelistToken", (ev) => { - return ( - ev.l1Token == l1TokenAddress && - ev.l2Token == l2Token.options.address && - ev.lastBridgeTime == expectedLastBridgeTime && - ev.bridgePool == bridgePool - ); - }); - }); - - it("Disable deposits", async () => { - // Trying to disable tokens from something other than the l2MessengerImpersonator should fail. - assert( - await didContractThrow( - depositBox.methods.setEnableDeposits(l2Token.options.address, false).send({ from: rando }) - ) - ); - - // Trying to call correctly via the L2 message impersonator, but from the wrong xDomainMessageSender should revert. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => rando); - - assert( - await didContractThrow( - depositBox.methods - .setEnableDeposits(l2Token.options.address, false) - .send({ from: predeploys.L2CrossDomainMessenger }) - ) - ); - - // Setting the l2CrossDomainMessengerMock to correctly mock the L1WithdrawContract should let the enable state change. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - - const tx = await depositBox.methods - .setEnableDeposits(l2Token.options.address, false) - .send({ from: predeploys.L2CrossDomainMessenger }); - - assert.equal((await depositBox.methods.whitelistedTokens(l2Token.options.address).call()).depositsEnabled, false); - - await assertEventEmitted(tx, depositBox, "DepositsEnabled", (ev) => { - return ev.l2Token === l2Token.options.address && ev.depositsEnabled == false; - }); - }); - }); - describe("Box bridging logic", () => { - let l2StandardBridge; - beforeEach(async function () { - // Whitelist the token in the deposit box. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: predeploys.L2CrossDomainMessenger }); - - // Setup the l2StandardBridge mock to validate cross-domain bridging occurs as expected. - l2StandardBridge = await deployContractMock("L2StandardBridge", { address: predeploys.L2StandardBridge }); - }); - it("Can initiate cross-domain bridging action", async () => { - // Deposit tokens as the user. - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Advance time enough to enable bridging of this token. - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay + 1) - .send({ from: deployer }); - - const tx = await depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }); - - await assertEventEmitted(tx, depositBox, "TokensBridged", (ev) => { - return ( - ev.l2Token == l2Token.options.address && - ev.numberOfTokensBridged == depositAmount && - ev.l1Gas == 0 && - ev.caller == rando - ); - }); - - // We should be able to check the mock L2 Standard bridge and see that there was a function call to the withdrawTo - // method called by the Deposit box for the correct token, amount and l1Recipient. - const tokenBridgingCallsToBridge = l2StandardBridge.smocked.withdrawTo.calls; - assert.equal(tokenBridgingCallsToBridge.length, 1); // only 1 call - const call = tokenBridgingCallsToBridge[0]; - assert.equal(call._l2Token, l2Token.options.address); // right token. - assert.equal(call._to, bridgePool); // right recipient. - assert.equal(call._amount.toString(), depositAmount); // right amount. We deposited 50e18. - assert.equal(call._l1Gas.toString(), 0); // right amount. We deposited 50e18. - assert.equal(call._data.toString(), "0x"); // right data. - }); - it("Reverts if not enough time elapsed", async () => { - // Deposit tokens as the user. - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Dont advance the timer by minimumBridgingDelay. Should revert. - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts on bridging 0 tokens", async () => { - // Don't do any deposits. balance should be zero and should revert as 0 token bridge action. - assert.equal(await l2Token.methods.balanceOf(depositBox.options.address).call(), "0"); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts if token not whitelisted", async () => { - // Create a new ERC20 and mint them directly to he depositBox.. Bridging should fail as not whitelisted. - const l2Token_nonWhitelisted = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.addMember(1, deployer).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.mint(depositBox.options.address, toWei("100")).send({ from: deployer }); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/OVM_OETH_BridgeDepositBox.js b/packages/core/test/insured-bridge/OVM_OETH_BridgeDepositBox.js deleted file mode 100644 index a7c14300a3..0000000000 --- a/packages/core/test/insured-bridge/OVM_OETH_BridgeDepositBox.js +++ /dev/null @@ -1,210 +0,0 @@ -const hre = require("hardhat"); -const { web3 } = hre; -const { predeploys } = require("@eth-optimism/contracts"); -const { didContractThrow } = require("@uma/common"); -const { getContract, assertEventEmitted, assertEventNotEmitted } = hre; - -const { toWei } = web3.utils; - -const { assert } = require("chai"); - -const { deployContractMock } = require("../helpers/SmockitHelper"); - -// Tested contract -const BridgeDepositBox = getContract("OVM_OETH_BridgeDepositBox"); - -// Helper contracts -const Weth9 = getContract("WETH9"); -const Token = getContract("ExpandedERC20"); -const Timer = getContract("Timer"); - -// Contract objects -let depositBox, l2CrossDomainMessengerMock, l1TokenAddress, l1WethAddress, timer, l2Weth, l2Token, l2EthAddress; - -// As these tests are in the context of l2, we dont have the deployed notion of an "L1 Token". The L1 token is within -// another domain (L1). Therefore we can generate random addresses to represent the L1 tokens. -l1TokenAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); -l1WethAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - -// We don't test whether L2 ETH is sent so we'll set it to a random address and just check that `bridgeTokens` correctly -// calls `StandardBridge.withdrawTo` with the `_l2Token` set to `l2Eth` instead of the `l2Weth` token. This happens -// because the StandardBridge first unwraps `l2Weth --> l2Eth` before bridging it, because the OVM StandardBridge -// cannot handle `l2Weth` at the moment. -l2EthAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); - -const minimumBridgingDelay = 60; // L2->L1 token bridging must wait at least this time. -const depositAmount = toWei("50"); -const slowRelayFeePct = toWei("0.005"); -const instantRelayFeePct = toWei("0.005"); -const quoteTimestampOffset = 60; // 60 seconds into the past. -const chainId = 10; // Optimism mainnet chain ID. - -describe("OVM_OETH_BridgeDepositBox", () => { - // Account objects - let accounts, deployer, user1, bridgeAdmin, rando, bridgePool, bridgePoolWeth, wethWrapper; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [deployer, user1, bridgeAdmin, rando, bridgePool, bridgePoolWeth, wethWrapper] = accounts; - - timer = await Timer.new().send({ from: deployer }); - }); - - beforeEach(async function () { - // Initialize the cross domain massager messenger mock at the address of the OVM pre-deploy. The OVM will always use - // this address for L1<->L2 messaging. Seed this address with some funds so it can send transactions. - l2CrossDomainMessengerMock = await deployContractMock("L2CrossDomainMessenger", { - address: predeploys.L2CrossDomainMessenger, - }); - await web3.eth.sendTransaction({ from: deployer, to: predeploys.L2CrossDomainMessenger, value: toWei("1") }); - - // Deploy and mintL2 token contracts: - // - WETH - l2Weth = await Weth9.new().send({ from: deployer }); - await l2Weth.methods.deposit().send({ from: user1, value: toWei("100") }); - // - Normal ERC20 - l2Token = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token.methods.addMember(1, deployer).send({ from: deployer }); - await l2Token.methods.mint(user1, toWei("100")).send({ from: deployer }); - - depositBox = await BridgeDepositBox.new( - bridgeAdmin, - minimumBridgingDelay, - chainId, - l1WethAddress, - l2EthAddress, - wethWrapper, - timer.options.address - ).send({ from: deployer }); - }); - describe("WETH: Box bridging logic", () => { - let l2StandardBridge; - beforeEach(async function () { - // Whitelist WETH in the deposit box. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - await depositBox.methods - .whitelistToken(l1WethAddress, l2Weth.options.address, bridgePoolWeth) - .send({ from: predeploys.L2CrossDomainMessenger }); - - // Setup the l2StandardBridge mock to validate cross-domain bridging occurs as expected. - l2StandardBridge = await deployContractMock("L2StandardBridge", { address: predeploys.L2StandardBridge }); - }); - it("Can initiate cross-domain bridging action", async () => { - // Deposit tokens as the user. - await l2Weth.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await depositBox.methods - .deposit(user1, l2Weth.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Advance time enough to enable bridging of this token. - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay + 1) - .send({ from: deployer }); - - const tx = await depositBox.methods.bridgeTokens(l2Weth.options.address, 0).send({ from: rando }); - - await assertEventEmitted(tx, depositBox, "TokensBridged", (ev) => { - return ( - ev.l2Token == l2EthAddress && ev.numberOfTokensBridged == depositAmount && ev.l1Gas == 0 && ev.caller == rando - ); - }); - - // We should be able to check the mock L2 Standard bridge and see that there was a function call to the withdrawTo - // method called by the Deposit box for the correct token, amount and l1Recipient. - const tokenBridgingCallsToBridge = l2StandardBridge.smocked.withdrawTo.calls; - assert.equal(tokenBridgingCallsToBridge.length, 1); // only 1 call - const call = tokenBridgingCallsToBridge[0]; - assert.equal(call._l2Token, l2EthAddress); // Bridging WETH should unwrap the WETH and bridge l2ETH instead - // of the l2WETH contract, because the OVM standard bridge cannot handle WETH at the moment. - assert.equal(call._to, wethWrapper); // Bridging WETH should set WETH wrapper as recipient, not the whitelisted - // BridgePool contract. - assert.equal(call._amount.toString(), depositAmount); // right amount. We deposited 50e18. - assert.equal(call._l1Gas.toString(), 0); // right amount. We deposited 50e18. - assert.equal(call._data.toString(), "0x"); // right data. - - // Check that WETH.withdraw was called and the expected event was emitted. - await assertEventEmitted(tx, l2Weth, "Withdrawal", (ev) => { - return ev.src == depositBox.options.address && ev.wad.toString() == depositAmount; - }); - }); - }); - describe("Non-WETH ERC20: Box bridging logic", () => { - let l2StandardBridge; - beforeEach(async function () { - // Whitelist the token in the deposit box. - l2CrossDomainMessengerMock.smocked.xDomainMessageSender.will.return.with(() => bridgeAdmin); - await depositBox.methods - .whitelistToken(l1TokenAddress, l2Token.options.address, bridgePool) - .send({ from: predeploys.L2CrossDomainMessenger }); - - // Setup the l2StandardBridge mock to validate cross-domain bridging occurs as expected. - l2StandardBridge = await deployContractMock("L2StandardBridge", { address: predeploys.L2StandardBridge }); - }); - it("Can initiate cross-domain bridging action", async () => { - // Deposit tokens as the user. - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Advance time enough to enable bridging of this token. - await timer.methods - .setCurrentTime(Number(await timer.methods.getCurrentTime().call()) + minimumBridgingDelay + 1) - .send({ from: deployer }); - - const tx = await depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }); - - await assertEventEmitted(tx, depositBox, "TokensBridged", (ev) => { - return ( - ev.l2Token == l2Token.options.address && - ev.numberOfTokensBridged == depositAmount && - ev.l1Gas == 0 && - ev.caller == rando - ); - }); - - // We should be able to check the mock L2 Standard bridge and see that there was a function call to the withdrawTo - // method called by the Deposit box for the correct token, amount and l1Recipient. - const tokenBridgingCallsToBridge = l2StandardBridge.smocked.withdrawTo.calls; - assert.equal(tokenBridgingCallsToBridge.length, 1); // only 1 call - const call = tokenBridgingCallsToBridge[0]; - assert.equal(call._l2Token, l2Token.options.address); // right token. - assert.equal(call._to, bridgePool); // right recipient is BridgePool contract, not Weth wrapper. - assert.equal(call._amount.toString(), depositAmount); // right amount. We deposited 50e18. - assert.equal(call._l1Gas.toString(), 0); // right amount. We deposited 50e18. - assert.equal(call._data.toString(), "0x"); // right data. - - // Check that WETH.withdraw was not called. - await assertEventNotEmitted(tx, l2Weth, "Withdrawal"); - }); - it("Reverts if not enough time elapsed", async () => { - // Deposit tokens as the user. - await l2Token.methods.approve(depositBox.options.address, toWei("100")).send({ from: user1 }); - const quoteTimestamp = Number(await timer.methods.getCurrentTime().call()) + quoteTimestampOffset; - await depositBox.methods - .deposit(user1, l2Token.options.address, depositAmount, slowRelayFeePct, instantRelayFeePct, quoteTimestamp) - .send({ from: user1 }); - - // Dont advance the timer by minimumBridgingDelay. Should revert. - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts on bridging 0 tokens", async () => { - // Don't do any deposits. balance should be zero and should revert as 0 token bridge action. - assert.equal(await l2Token.methods.balanceOf(depositBox.options.address).call(), "0"); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - it("Reverts if token not whitelisted", async () => { - // Create a new ERC20 and mint them directly to he depositBox.. Bridging should fail as not whitelisted. - const l2Token_nonWhitelisted = await Token.new("L2 Wrapped Ether", "WETH", 18).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.addMember(1, deployer).send({ from: deployer }); - await l2Token_nonWhitelisted.methods.mint(depositBox.options.address, toWei("100")).send({ from: deployer }); - - assert(await didContractThrow(depositBox.methods.bridgeTokens(l2Token.options.address, 0).send({ from: rando }))); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/OptimismMessenger.js b/packages/core/test/insured-bridge/OptimismMessenger.js deleted file mode 100644 index a5478c8755..0000000000 --- a/packages/core/test/insured-bridge/OptimismMessenger.js +++ /dev/null @@ -1,236 +0,0 @@ -const hre = require("hardhat"); -const { web3 } = hre; -const { ZERO_ADDRESS, didContractThrow, interfaceName } = require("@uma/common"); -const { getContract } = hre; -const { utf8ToHex, toWei } = web3.utils; - -const { assert } = require("chai"); - -const { deployContractMock } = require("../helpers/SmockitHelper"); - -// Tested contracts -const OVM_L1CrossDomainMessengerMock = getContract("OVM_L1CrossDomainMessengerMock"); -const Optimism_Messenger = getContract("Optimism_Messenger"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgePool = getContract("BridgePool"); -const Timer = getContract("Timer"); -const Store = getContract("Store"); -const Finder = getContract("Finder"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); -const IdentifierWhitelist = getContract("IdentifierWhitelist"); -const AddressWhitelist = getContract("AddressWhitelist"); -const ERC20 = getContract("ERC20"); - -// Contract objects -let optimismMessenger; -let bridgeAdmin; -let finder; -let store; -let l1CrossDomainMessengerMock; -let depositBox; -let identifierWhitelist; -let collateralWhitelist; -let timer; - -// Test function inputs -const defaultGasLimit = 1_000_000; -const defaultGasPrice = toWei("1", "gwei"); -const defaultIdentifier = utf8ToHex("IS_CROSS_CHAIN_RELAY_VALID"); -const defaultLiveness = 7200; -const defaultProposerBondPct = toWei("0.05"); -const lpFeeRatePerSecond = toWei("0.0000015"); -const defaultBridgingDelay = 60; -const chainId = "10"; -let l1Token; -let l2Token; -let bridgePool; - -describe("OptimismMessenger integration with BridgeAdmin", () => { - let accounts, owner, rando, rando2, depositBoxImpersonator; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [owner, rando, rando2, depositBoxImpersonator] = accounts; - l1Token = await ERC20.new("", "").send({ from: owner }); - l2Token = rando2; - - timer = await Timer.new().send({ from: owner }); - finder = await Finder.new().send({ from: owner }); - collateralWhitelist = await AddressWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.CollateralWhitelist), collateralWhitelist.options.address) - .send({ from: owner }); - - identifierWhitelist = await IdentifierWhitelist.new().send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.IdentifierWhitelist), identifierWhitelist.options.address) - .send({ from: owner }); - await identifierWhitelist.methods.addSupportedIdentifier(defaultIdentifier).send({ from: owner }); - - // The initialization of the bridge pool requires there to be an address of both the store and the SkinnyOptimisticOracle - // set in the finder. These tests dont use these contracts but there are never the less needed for deployment. - store = await Store.new({ rawValue: "0" }, { rawValue: "0" }, timer.options.address).send({ from: owner }); - await store.methods.setFinalFee(l1Token.options.address, { rawValue: toWei("1") }).send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.Store), store.options.address) - .send({ from: owner }); - await finder.methods - .changeImplementationAddress(utf8ToHex(interfaceName.SkinnyOptimisticOracle), rando) - .send({ from: owner }); - }); - beforeEach(async function () { - l1CrossDomainMessengerMock = await deployContractMock( - "OVM_L1CrossDomainMessengerMock", - {}, - OVM_L1CrossDomainMessengerMock - ); - - optimismMessenger = await Optimism_Messenger.new(l1CrossDomainMessengerMock.options.address).send({ from: owner }); - - bridgeAdmin = await BridgeAdmin.new( - finder.options.address, - defaultLiveness, - defaultProposerBondPct, - defaultIdentifier - ).send({ from: owner }); - - bridgePool = await BridgePool.new( - "LP Token", - "LPT", - bridgeAdmin.options.address, - l1Token.options.address, - lpFeeRatePerSecond, - false, // not set to weth pool - timer.options.address - ).send({ from: owner }); - - depositBox = await BridgeDepositBox.new( - bridgeAdmin.options.address, - defaultBridgingDelay, - ZERO_ADDRESS, // weth address. Weth mode not used in these tests - ZERO_ADDRESS // timer address - ).send({ from: owner }); - }); - it("relayMessage only callable by owner", async function () { - const relayMessageTxn = optimismMessenger.methods.relayMessage( - depositBox.options.address, - owner, - 0, - defaultGasLimit, - defaultGasPrice, - 0, - "0x" - ); - assert(await didContractThrow(relayMessageTxn.send({ from: rando }))); - assert.ok(await relayMessageTxn.send({ from: owner })); - }); - describe("Cross domain Admin functions", () => { - beforeEach(async function () { - await optimismMessenger.methods.transferOwnership(bridgeAdmin.options.address).send({ from: owner }); - }); - it("Whitelist tokens", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, optimismMessenger.options.address) - .send({ from: owner }); - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - 0 - ) - .send({ from: owner }); - const whitelistCallToMessengerCall = l1CrossDomainMessengerMock.smocked.sendMessage.calls[0]; - - // Validate xchain message - assert.equal( - whitelistCallToMessengerCall._target, - depositBoxImpersonator, - "xchain target should be deposit contract" - ); - const expectedAbiData = depositBox.methods - .whitelistToken(l1Token.options.address, l2Token, bridgePool.options.address) - .encodeABI(); - assert.equal(whitelistCallToMessengerCall._message, expectedAbiData, "xchain message bytes unexpected"); - assert.equal(whitelistCallToMessengerCall._gasLimit, defaultGasLimit, "xchain gas limit unexpected"); - }); - it("Set bridge admin", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, optimismMessenger.options.address) - .send({ from: owner }); - await bridgeAdmin.methods - .setCrossDomainAdmin(chainId, rando, 0, defaultGasLimit, defaultGasPrice, 0) - .send({ from: owner }); - const setAdminCallToMessengerCall = l1CrossDomainMessengerMock.smocked.sendMessage.calls[0]; - - // Validate xchain message - assert.equal( - setAdminCallToMessengerCall._target, - depositBoxImpersonator, - "xchain target should be deposit contract" - ); - const expectedAbiData = depositBox.methods.setCrossDomainAdmin(rando).encodeABI(); - assert.equal(setAdminCallToMessengerCall._message, expectedAbiData, "xchain message bytes unexpected"); - assert.equal(setAdminCallToMessengerCall._gasLimit, defaultGasLimit, "xchain gas limit unexpected"); - }); - it("Set minimum bridge delay", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, optimismMessenger.options.address) - .send({ from: owner }); - await bridgeAdmin.methods - .setMinimumBridgingDelay(chainId, defaultBridgingDelay, 0, defaultGasLimit, defaultGasPrice, 0) - .send({ from: owner }); - const setDelayCallToMessengerCall = l1CrossDomainMessengerMock.smocked.sendMessage.calls[0]; - - // Validate xchain message - assert.equal( - setDelayCallToMessengerCall._target, - depositBoxImpersonator, - "xchain target should be deposit contract" - ); - const expectedAbiData = depositBox.methods.setMinimumBridgingDelay(defaultBridgingDelay).encodeABI(); - assert.equal(setDelayCallToMessengerCall._message, expectedAbiData, "xchain message bytes unexpected"); - assert.equal(setDelayCallToMessengerCall._gasLimit, defaultGasLimit, "xchain gas limit unexpected"); - }); - it("Pause deposits", async () => { - await bridgeAdmin.methods - .setDepositContract(chainId, depositBoxImpersonator, optimismMessenger.options.address) - .send({ from: owner }); - - await collateralWhitelist.methods.addToWhitelist(l1Token.options.address).send({ from: owner }); - - await bridgeAdmin.methods - .whitelistToken( - chainId, - l1Token.options.address, - l2Token, - bridgePool.options.address, - 0, - defaultGasLimit, - defaultGasPrice, - 0 - ) - .send({ from: owner }); - - await bridgeAdmin.methods - .setEnableDepositsAndRelays(chainId, l1Token.options.address, false, 0, defaultGasLimit, defaultGasPrice, 0) - .send({ from: owner }); - const setPauseCallToMessengerCall = l1CrossDomainMessengerMock.smocked.sendMessage.calls[0]; - - // Validate xchain message - assert.equal( - setPauseCallToMessengerCall._target, - depositBoxImpersonator, - "xchain target should be deposit contract" - ); - const expectedAbiData = depositBox.methods.setEnableDeposits(l2Token, false).encodeABI(); - assert.equal(setPauseCallToMessengerCall._message, expectedAbiData, "xchain message bytes unexpected"); - assert.equal(setPauseCallToMessengerCall._gasLimit, defaultGasLimit, "xchain gas limit unexpected"); - }); - }); -}); diff --git a/packages/core/test/insured-bridge/Optimism_Wrapper.js b/packages/core/test/insured-bridge/Optimism_Wrapper.js deleted file mode 100644 index a0b0f6b270..0000000000 --- a/packages/core/test/insured-bridge/Optimism_Wrapper.js +++ /dev/null @@ -1,55 +0,0 @@ -// These tests are meant to be run within the `hardhat` network (not OVM/AVM). They test the bridge deposit box logic -// and ignore all l2/l1 cross chain admin logic. For those tests see AVM_BridgeDepositBox & OVM_BridgeDepositBox for -// L2 specific unit tests that valid logic pertaining to those chains. - -const hre = require("hardhat"); -const { getContract, assertEventEmitted } = hre; -const { assert } = require("chai"); -const { web3 } = hre; -const { toWei } = web3.utils; -const { didContractThrow } = require("@uma/common"); - -const Weth9 = getContract("WETH9"); -const EthWrapper = getContract("Optimism_Wrapper"); - -// Contract objects -let wrapper, weth; - -const transferAmount = toWei("10"); - -describe("Optimism_Wrapper", () => { - let accounts, deployer, bridgePool; - - before(async function () { - accounts = await web3.eth.getAccounts(); - [deployer, bridgePool] = accounts; - }); - beforeEach(async function () { - weth = await Weth9.new().send({ from: deployer }); - wrapper = await EthWrapper.new(weth.options.address, bridgePool).send({ from: deployer }); - }); - it("wrapAndTransfer", async () => { - // Send ETH to contract, which will trigger its fallback() method and execute wrapAndTransfer(). - const txn = await web3.eth.sendTransaction({ from: deployer, to: wrapper.options.address, value: transferAmount }); - await assertEventEmitted(txn, weth, "Deposit", (ev) => { - return ev.dst == wrapper.options.address && ev.wad.toString() == transferAmount; - }); - - // Wrapper contract should have no ETH or WETH balance because it wrapped any received and sent to bridge pool. - assert.equal((await weth.methods.balanceOf(wrapper.options.address).call()).toString(), "0"); - assert.equal((await web3.eth.getBalance(wrapper.options.address)).toString(), "0"); - assert.equal((await weth.methods.balanceOf(bridgePool).call()).toString(), transferAmount); - }); - it("changeBridgePool", async () => { - const changeBridgePool = wrapper.methods.changeBridgePool(deployer); - - // Only owner can call - assert(await didContractThrow(changeBridgePool.send({ from: bridgePool }))); - - const txn = await changeBridgePool.send({ from: deployer }); - await assertEventEmitted(txn, wrapper, "ChangedBridgePool", (ev) => { - return ev.bridgePool === deployer; - }); - assert.equal(await wrapper.methods.bridgePool().call(), deployer); - }); -}); diff --git a/packages/core/test/insured-bridge/RateModelStore.js b/packages/core/test/insured-bridge/RateModelStore.js deleted file mode 100644 index 3abb19aa02..0000000000 --- a/packages/core/test/insured-bridge/RateModelStore.js +++ /dev/null @@ -1,42 +0,0 @@ -const hre = require("hardhat"); -const { getContract, assertEventEmitted } = hre; -const { didContractThrow } = require("@uma/common"); -const { assert } = require("chai"); - -const RateModelStore = getContract("RateModelStore"); - -describe("RateModelStore", function () { - let accounts; - let owner; - let user; - - let store; - let l1Token; - before(async function () { - accounts = await web3.eth.getAccounts(); - [owner, user, l1Token] = accounts; - - store = await RateModelStore.new().send({ from: owner }); - }); - - it("Update rate model for L1 token", async function () { - console.log(await store.methods.l1TokenRateModels(l1Token).call()); - assert.equal( - await store.methods.l1TokenRateModels(l1Token).call(), - "" // Empty string - ); - - const newRateModel = JSON.stringify({ key: "value" }); - - const updateRateModel = store.methods.updateRateModel(l1Token, newRateModel); - - // Only owner can update. - assert(await didContractThrow(updateRateModel.send({ from: user }))); - - const txn = await updateRateModel.send({ from: owner }); - assert.equal(await store.methods.l1TokenRateModels(l1Token).call(), newRateModel); - assertEventEmitted(txn, store, "UpdatedRateModel", (ev) => { - return ev.rateModel === newRateModel && ev.l1Token === l1Token; - }); - }); -}); diff --git a/packages/financial-templates-lib/test/clients/InsuredBridgeL1Client.js b/packages/financial-templates-lib/test/clients/InsuredBridgeL1Client.js index 2ada6c41c2..30436e43ab 100644 --- a/packages/financial-templates-lib/test/clients/InsuredBridgeL1Client.js +++ b/packages/financial-templates-lib/test/clients/InsuredBridgeL1Client.js @@ -8,8 +8,6 @@ const winston = require("winston"); const { assert } = require("chai"); const chainId = 10; const Messenger = getContract("MessengerMock"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgePool = getContract("BridgePool"); const Finder = getContract("Finder"); const IdentifierWhitelist = getContract("IdentifierWhitelist"); const AddressWhitelist = getContract("AddressWhitelist"); @@ -18,7 +16,16 @@ const Store = getContract("Store"); const ERC20 = getContract("ExpandedERC20"); const Timer = getContract("Timer"); const MockOracle = getContract("MockOracleAncillary"); -const RateModelStore = getContract("RateModelStore"); + +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const RateModelStore = getContract("RateModelStore", { + abi: getAbi("RateModelStore"), + bytecode: getBytecode("RateModelStore"), +}); +const BridgePool = getContract("BridgePool", { abi: getAbi("BridgePool"), bytecode: getBytecode("BridgePool") }); +const BridgeAdmin = getContract("BridgeAdmin", { abi: getAbi("BridgeAdmin"), bytecode: getBytecode("BridgeAdmin") }); // Client to test const { diff --git a/packages/financial-templates-lib/test/clients/InsuredBridgeL2Client.js b/packages/financial-templates-lib/test/clients/InsuredBridgeL2Client.js index c9b461e6f4..f2e0976dad 100644 --- a/packages/financial-templates-lib/test/clients/InsuredBridgeL2Client.js +++ b/packages/financial-templates-lib/test/clients/InsuredBridgeL2Client.js @@ -15,10 +15,17 @@ const { ZERO_ADDRESS } = require("@uma/common"); // Helper contracts const chainId = 10; -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); const Token = getContract("ExpandedERC20"); const Timer = getContract("Timer"); +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const BridgeDepositBox = getContract("BridgeDepositBoxMock", { + abi: getAbi("BridgeDepositBoxMock"), + bytecode: getBytecode("BridgeDepositBoxMock"), +}); + // Contract objects let depositBox, l1TokenAddress, l2Token, timer, client; diff --git a/packages/financial-templates-lib/test/price-feed/InsuredBridgePriceFeed.js b/packages/financial-templates-lib/test/price-feed/InsuredBridgePriceFeed.js index 4a5bb5f588..d2eb63fca6 100644 --- a/packages/financial-templates-lib/test/price-feed/InsuredBridgePriceFeed.js +++ b/packages/financial-templates-lib/test/price-feed/InsuredBridgePriceFeed.js @@ -19,9 +19,6 @@ const toBNWei = (number) => toBN(toWei(number.toString()).toString()); const chainId = 10; const Messenger = getContract("MessengerMock"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgePool = getContract("BridgePool"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); const Finder = getContract("Finder"); const IdentifierWhitelist = getContract("IdentifierWhitelist"); const AddressWhitelist = getContract("AddressWhitelist"); @@ -29,7 +26,23 @@ const OptimisticOracle = getContract("SkinnyOptimisticOracle"); const Store = getContract("Store"); const ERC20 = getContract("ExpandedERC20"); const Timer = getContract("Timer"); -const RateModelStore = getContract("RateModelStore"); + +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const BridgeDepositBox = getContract("BridgeDepositBoxMock", { + abi: getAbi("BridgeDepositBoxMock"), + bytecode: getBytecode("BridgeDepositBoxMock"), +}); + +const BridgePool = getContract("BridgePool", { abi: getAbi("BridgePool"), bytecode: getBytecode("BridgePool") }); + +const BridgeAdmin = getContract("BridgeAdmin", { abi: getAbi("BridgeAdmin"), bytecode: getBytecode("BridgeAdmin") }); + +const RateModelStore = getContract("RateModelStore", { + abi: getAbi("RateModelStore"), + bytecode: getBytecode("RateModelStore"), +}); // Pricefeed to test const { InsuredBridgePriceFeed } = require("../../dist/price-feed/InsuredBridgePriceFeed"); diff --git a/packages/insured-bridge-relayer/test/CrossDomainFinalizer.ts b/packages/insured-bridge-relayer/test/CrossDomainFinalizer.ts index a9ba5c57ee..58285c6896 100644 --- a/packages/insured-bridge-relayer/test/CrossDomainFinalizer.ts +++ b/packages/insured-bridge-relayer/test/CrossDomainFinalizer.ts @@ -26,9 +26,6 @@ const toBNWei = (number: string | number) => toBN(toWei(number.toString()).toStr // Helper contracts const Messenger = getContract("MessengerMock"); -const BridgePool = getContract("BridgePool"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); const Finder = getContract("Finder"); const IdentifierWhitelist = getContract("IdentifierWhitelist"); const AddressWhitelist = getContract("AddressWhitelist"); @@ -37,7 +34,29 @@ const Store = getContract("Store"); const ERC20 = getContract("ExpandedERC20"); const Timer = getContract("Timer"); const MockOracle = getContract("MockOracleAncillary"); -const RateModelStore = getContract("RateModelStore"); + +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const BridgeDepositBox = getContract("BridgeDepositBoxMock", { + abi: getAbi("BridgeDepositBoxMock"), + bytecode: getBytecode("BridgeDepositBoxMock"), +}); + +const BridgePool = getContract("BridgePool", { + abi: getAbi("BridgePool"), + bytecode: getBytecode("BridgePool"), +}); + +const BridgeAdmin = getContract("BridgeAdmin", { + abi: getAbi("BridgeAdmin"), + bytecode: getBytecode("BridgeAdmin"), +}); + +const RateModelStore = getContract("RateModelStore", { + abi: getAbi("RateModelStore"), + bytecode: getBytecode("RateModelStore"), +}); // Contract objects let messenger: any; diff --git a/packages/insured-bridge-relayer/test/MulticallBundler.ts b/packages/insured-bridge-relayer/test/MulticallBundler.ts index 47ef149c32..13e21a261f 100644 --- a/packages/insured-bridge-relayer/test/MulticallBundler.ts +++ b/packages/insured-bridge-relayer/test/MulticallBundler.ts @@ -40,7 +40,7 @@ describe("MulticallBundler.ts", function () { it("Sends single transaction", async function () { multicallBundler!.addTransactions({ transaction: txnCast(multicaller!.methods.add("1")) }); await multicallBundler!.send(); - console.log(await multicallBundler!.waitForMine()); + await multicallBundler!.waitForMine(); assert.equal((await multicaller!.methods.value().call()).toString(), "1"); }); diff --git a/packages/insured-bridge-relayer/test/Relayer.ts b/packages/insured-bridge-relayer/test/Relayer.ts index 7f45e6ea24..c017aeaf78 100644 --- a/packages/insured-bridge-relayer/test/Relayer.ts +++ b/packages/insured-bridge-relayer/test/Relayer.ts @@ -31,9 +31,6 @@ import type { BN } from "@uma/common"; // Helper contracts const Messenger = getContract("MessengerMock"); -const BridgePool = getContract("BridgePool"); -const BridgeAdmin = getContract("BridgeAdmin"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); const Finder = getContract("Finder"); const IdentifierWhitelist = getContract("IdentifierWhitelist"); const AddressWhitelist = getContract("AddressWhitelist"); @@ -42,7 +39,29 @@ const Store = getContract("Store"); const ERC20 = getContract("ExpandedERC20"); const Timer = getContract("Timer"); const MockOracle = getContract("MockOracleAncillary"); -const RateModelStore = getContract("RateModelStore"); + +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const BridgeDepositBox = getContract("BridgeDepositBoxMock", { + abi: getAbi("BridgeDepositBoxMock"), + bytecode: getBytecode("BridgeDepositBoxMock"), +}); + +const BridgePool = getContract("BridgePool", { + abi: getAbi("BridgePool"), + bytecode: getBytecode("BridgePool"), +}); + +const BridgeAdmin = getContract("BridgeAdmin", { + abi: getAbi("BridgeAdmin"), + bytecode: getBytecode("BridgeAdmin"), +}); + +const RateModelStore = getContract("RateModelStore", { + abi: getAbi("RateModelStore"), + bytecode: getBytecode("RateModelStore"), +}); // Contract objects let messenger: any; diff --git a/packages/insured-bridge-relayer/test/index.ts b/packages/insured-bridge-relayer/test/index.ts index 1a17baeae4..dd8c94f79b 100644 --- a/packages/insured-bridge-relayer/test/index.ts +++ b/packages/insured-bridge-relayer/test/index.ts @@ -30,9 +30,6 @@ const networks = [ { chainId: 42161, port: 8888 }, ]; const Messenger = getContract("MessengerMock"); -const BridgePool = getContract("BridgePool"); -const BridgeDepositBox = getContract("BridgeDepositBoxMock"); -const BridgeAdmin = getContract("BridgeAdmin"); const Finder = getContract("Finder"); const IdentifierWhitelist = getContract("IdentifierWhitelist"); const AddressWhitelist = getContract("AddressWhitelist"); @@ -41,7 +38,29 @@ const Store = getContract("Store"); const ERC20 = getContract("ExpandedERC20"); const Timer = getContract("Timer"); const MockOracle = getContract("MockOracleAncillary"); -const RateModelStore = getContract("RateModelStore"); + +// Pull in contracts from contracts-node sourced from the across repo. +const { getAbi, getBytecode } = require("@uma/contracts-node"); + +const BridgeDepositBox = getContract("BridgeDepositBoxMock", { + abi: getAbi("BridgeDepositBoxMock"), + bytecode: getBytecode("BridgeDepositBoxMock"), +}); + +const BridgePool = getContract("BridgePool", { + abi: getAbi("BridgePool"), + bytecode: getBytecode("BridgePool"), +}); + +const BridgeAdmin = getContract("BridgeAdmin", { + abi: getAbi("BridgeAdmin"), + bytecode: getBytecode("BridgeAdmin"), +}); + +const RateModelStore = getContract("RateModelStore", { + abi: getAbi("RateModelStore"), + bytecode: getBytecode("RateModelStore"), +}); // Contract objects let messenger: any; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index f602bda2ff..66f6ad7946 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -46,8 +46,11 @@ export type SerializableEvent = Omit< // this convoluted type is meant to cast events to the types you need based on the contract and event name // example: type NewContractRegistered = GetEventType; +// TODO: the any below is a hacky solution because some typechain types fail to resolve due to +// incompatible TypedEventFilter and TypedEvent types. This will be fixed by upgrading typechain +// to a version where Ethers events are exported as first class types. export type GetEventType = ReturnType< ContractType["filters"][EventName] extends Callable ? ContractType["filters"][EventName] : never > extends TypedEventFilter - ? TypedEvent + ? TypedEvent : never; diff --git a/yarn.lock b/yarn.lock index 8813f9ecf7..b81db8ffa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@across-protocol/contracts@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-0.1.2.tgz#8c5b424956344ba91134f5fb1ba55d57fde114cb" - integrity sha512-5Ep2Tm8tXWafx+DBVZr0NKLCj0vEm7sqCfI1ZPmldqE8STJud/l8c2rRgg+hFfuQWxhYMhpk3ygv3gBJ8VRISA== +"@across-protocol/contracts@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-0.1.4.tgz#64b3d91e639d2bb120ea94ddef3d160967047fa5" + integrity sha512-y9FVRSFdPgEdGWBcf8rUmmzdYhzGdy0752HwpaAFtMJ1pn+HFgNaI0EZc/UudMKIPOkk+/BxPIHYPy7tKad5/A== dependencies: "@eth-optimism/contracts" "^0.5.5" "@openzeppelin/contracts" "4.1.0"