Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions contracts/BridgeAdmin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./interfaces/BridgeAdminInterface.sol";

contract BridgeAdmin is BridgeAdminInterface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just import this from uma/core?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see that this would be a brand new contract

// Finder used to point to latest OptimisticOracle and other DVM contracts.
address public finder;

constructor(address _finder) {
finder = _finder;
}
}
99 changes: 93 additions & 6 deletions contracts/HubPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import "@uma/core/contracts/common/implementation/Testable.sol";
import "@uma/core/contracts/common/implementation/Lockable.sol";
import "@uma/core/contracts/common/implementation/MultiCaller.sol";
import "@uma/core/contracts/common/implementation/ExpandedERC20.sol";
import "@uma/core/contracts/oracle/interfaces/FinderInterface.sol";
import "@uma/core/contracts/oracle/interfaces/StoreInterface.sol";
import "@uma/core/contracts/oracle/implementation/Constants.sol";

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
Expand All @@ -26,13 +29,41 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
bool isEnabled;
}

WETH9Like public l1Weth;
enum RefundRequestStatus {
Pending,
Finalized,
Disputed
}

struct RelayerRefundRequest {
bytes32 poolRebalanceProof;
bytes32 destinationDistributionProof;
RefundRequestStatus status;
}

RelayerRefundRequest[] public relayerRefundRequests;

// Whitelist of origin token to destination token routings to be used by off-chain agents.
mapping(address => mapping(uint256 => address)) public whitelistedRoutes;

mapping(address => LPToken) public lpTokens; // Mapping of L1TokenAddress to the associated LPToken.

// Address of L1Weth. Enable LPs to deposit/receive ETH, if they choose, when adding/removing liquidity.
WETH9Like public l1Weth;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general do we prefer storing contracts as addresses or as interfaces? I stored weth as an address in the SpokePool for example but we should just standardize

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye, good point. my main question which should inform this decision is is this more expensive to store? is this more expensive to use?

my main reason for doing it this way is it means you dont need to do any type casting when using the interface which makes things a bit easier.


// Token used to bond the data worker for proposing relayer refund bundles.
IERC20 public bondToken;

// The computed bond amount as the UMA Store's final fee multiplied by the bondTokenFinalFeeMultiplier.
uint256 public bondAmount;

// There should only ever be one refund proposal pending at any point in time. This bool toggles accordingly.
bool public currentPendingRefundProposal = false;

event BondAmountSet(uint64 newBondMultiplier);

event BondTokenSet(address newBondMultiplier);

event LiquidityAdded(
address indexed l1Token,
uint256 amount,
Expand All @@ -47,14 +78,39 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
);
event WhitelistRoute(address originToken, uint256 destinationChainId, address destinationToken);

constructor(address _l1Weth, address _timerAddress) Testable(_timerAddress) {
event RelayerRefundRequested(
uint64 relayerRefundId,
uint256[] bundleEvaluationBlockNumbers,
bytes32 indexed poolRebalanceProof,
bytes32 indexed destinationDistributionProof,
address proposer
);

constructor(
uint256 _bondAmount,
address _bondToken,
address _l1Weth,
address _timerAddress
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: group similar types together

) Testable(_timerAddress) {
bondAmount = _bondAmount;
bondToken = IERC20(_bondToken);
l1Weth = WETH9Like(_l1Weth);
}

/*************************************************
* ADMIN FUNCTIONS *
*************************************************/

function setBondToken(address newBondToken) public onlyOwner {
bondToken = IERC20(newBondToken);
emit BondTokenSet(newBondToken);
}

function setBondTokenFinalFeeMultiplier(uint64 newBondAmount) public onlyOwner {
bondAmount = newBondAmount;
emit BondAmountSet(newBondAmount);
}

/**
* @notice Whitelist an origin token <-> destination token route.
*/
Expand Down Expand Up @@ -139,11 +195,36 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {

function liquidityUtilizationPostRelay(address token, uint256 relayedAmount) public returns (uint256) {}

/*************************************************
* DATA WORKER FUNCTIONS *
*************************************************/

function initiateRelayerRefund(
uint256[] memory bundleEvaluationBlockNumberForChain,
bytes32 chainBatchRepaymentProof,
bytes32 relayerRepaymentDistributionProof
) public {}
uint256[] memory bundleEvaluationBlockNumbers,
bytes32 poolRebalanceProof,
bytes32 destinationDistributionProof
) public {
require(currentPendingRefundProposal == false, "Only one proposal at a time");
currentPendingRefundProposal = true;
relayerRefundRequests.push(
RelayerRefundRequest({
poolRebalanceProof: poolRebalanceProof,
destinationDistributionProof: destinationDistributionProof,
status: RefundRequestStatus.Pending
})
);

// Pull bondAmount of bondToken from the caller.
bondToken.safeTransferFrom(msg.sender, address(this), bondAmount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is annoying, but how do we deal with a changing bond amount mid-refund? I know this code is to pull the bond, but do you think we need to store the bond amount for each refund request?

Copy link
Member Author

@chrismaree chrismaree Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just dont change it if there is a pending refund I guess. if we set the bond as a variable directly (not using the store) this becomes easier.


emit RelayerRefundRequested(
uint64(relayerRefundRequests.length - 1), // Index of the relayerRefundRequest within the array.
bundleEvaluationBlockNumbers,
poolRebalanceProof,
destinationDistributionProof,
msg.sender
);
}

function executeRelayerRefund(
uint256 relayerRefundRequestId,
Expand All @@ -155,6 +236,12 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
bytes32[] memory inclusionProof
) public {}

function disputeRelayerRefund() public {}

/*************************************************
* INTERNAL FUNCTIONS *
*************************************************/

function _exchangeRateCurrent() internal pure returns (uint256) {
return 1e18;
}
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/BridgeAdminInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

interface BridgeAdminInterface {
function finder() external view returns (address);
}
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,19 @@
"ethers": "^5.0.0",
"hardhat": "^2.8.3",
"hardhat-gas-reporter": "^1.0.4",
"husky": "^4.2.3",
"prettier": "^2.3.2",
"prettier-plugin-solidity": "^1.0.0-beta.13",
"pretty-quick": "^2.0.1",
"solhint": "^3.3.6",
"solidity-coverage": "^0.7.16",
"ts-node": "^10.1.0",
"typechain": "^5.1.2",
"typescript": "^4.5.2"
},
"husky": {
"hooks": {
"pre-commit": "echo '🏃‍♂️ Running pretty-quick on staged files' && pretty-quick --staged"
}
}
}
5 changes: 4 additions & 1 deletion test/HubPool.Fixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TokenRolesEnum } from "@uma/common";
import { getContractFactory } from "./utils";
import { bondAmount } from "./constants";
import { Contract } from "ethers";

export async function deployHubPoolTestHelperContracts(deployerWallet: any) {
Expand All @@ -14,7 +15,9 @@ export async function deployHubPoolTestHelperContracts(deployerWallet: any) {
await dai.addMember(TokenRolesEnum.MINTER, deployerWallet.address);

// Deploy the hubPool
const hubPool = await (await getContractFactory("HubPool", deployerWallet)).deploy(weth.address, timer.address);
const hubPool = await (
await getContractFactory("HubPool", deployerWallet)
).deploy(bondAmount, weth.address, weth.address, timer.address);

return { timer, weth, usdc, dai, hubPool };
}
Expand Down
44 changes: 44 additions & 0 deletions test/HubPool.RelayerRefund.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from "chai";
import { Contract } from "ethers";

import { ethers } from "hardhat";
import { ZERO_ADDRESS } from "@uma/common";
import { getContractFactory, SignerWithAddress, createRandomBytes32, seedWallet } from "./utils";
import { depositDestinationChainId, bondAmount } from "./constants";
import { deployHubPoolTestHelperContracts } from "./HubPool.Fixture";

let hubPool: Contract, weth: Contract, usdc: Contract;
let owner: SignerWithAddress, dataWorker: SignerWithAddress;

describe("HubPool Relayer Refund", function () {
before(async function () {
[owner, dataWorker] = await ethers.getSigners();
({ weth, hubPool, usdc } = await deployHubPoolTestHelperContracts(owner));
await seedWallet(dataWorker, [], weth, bondAmount);
});

it("Initialization of a relay correctly stores data, emits events and pulls the bond", async function () {
const bundleEvaluationBlockNumbers = [1, 2, 3];
const poolRebalanceProof = createRandomBytes32();
const destinationDistributionProof = createRandomBytes32();

await weth.connect(dataWorker).approve(hubPool.address, bondAmount);
await expect(
hubPool
.connect(dataWorker)
.initiateRelayerRefund(bundleEvaluationBlockNumbers, poolRebalanceProof, destinationDistributionProof)
)
.to.emit(hubPool, "RelayerRefundRequested")
.withArgs(0, bundleEvaluationBlockNumbers, poolRebalanceProof, destinationDistributionProof, dataWorker.address);
// Balances of the hubPool should have incremented by the bond and the dataWorker should have decremented by the bond.
expect(await weth.balanceOf(hubPool.address)).to.equal(bondAmount);
expect(await weth.balanceOf(dataWorker.address)).to.equal(0);

// Can only have one pending proposal at a time.
await expect(
hubPool
.connect(dataWorker)
.initiateRelayerRefund(bundleEvaluationBlockNumbers, poolRebalanceProof, destinationDistributionProof)
).to.be.revertedWith("Only one proposal at a time");
});
});
2 changes: 2 additions & 0 deletions test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export const depositDestinationChainId = 10;
export const depositRelayerFeePct = toWei("0.25");

export const depositQuoteTimeBuffer = 10 * 60; // 10 minutes

export const bondAmount = toWei("5"); // 5 ETH as the bond for proposing refund bundles.
4 changes: 4 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ export async function seedWallet(

if (weth) await weth.connect(walletToFund).deposit({ value: amountToSeedWith });
}

export function createRandomBytes32() {
return ethers.utils.hexlify(ethers.utils.randomBytes(32));
}
Loading