Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ba54cb9
nit
chrismaree Jan 28, 2022
84aad83
nit
chrismaree Jan 28, 2022
e971ff7
nit
chrismaree Jan 28, 2022
7a24fba
Update contracts/HubPool.sol
chrismaree Jan 31, 2022
2f99c84
review nit
chrismaree Jan 31, 2022
dcb052a
review nit
chrismaree Jan 31, 2022
487e7ea
feat(hubpool): Refactor hub pool to use 1D bitmap integer
chrismaree Jan 31, 2022
39ea052
nit
chrismaree Jan 31, 2022
febc4a6
WIP
chrismaree Feb 1, 2022
1ba6779
review nit
chrismaree Feb 1, 2022
1886cfd
Merge branch 'master' into chrismaree/third-pass-replayment-claim
chrismaree Feb 1, 2022
439138b
review nit
chrismaree Feb 1, 2022
b7e3ae2
nit
chrismaree Feb 1, 2022
1f6fc17
nit
chrismaree Feb 1, 2022
f64bf85
nit
chrismaree Feb 1, 2022
8bdcec7
nit
chrismaree Feb 1, 2022
89e642a
nit
chrismaree Feb 1, 2022
d6bc9bd
nit
chrismaree Feb 1, 2022
8de1b8f
nit
chrismaree Feb 1, 2022
98b2b52
nit
chrismaree Feb 2, 2022
eefd0c0
WIP
chrismaree Feb 2, 2022
5de87a2
WIP
chrismaree Feb 2, 2022
9d75d36
nit
chrismaree Feb 2, 2022
aca98e0
nit
chrismaree Feb 3, 2022
6307c53
Merge branch 'master' into chrismaree/fourth-pass-repayment-claim
chrismaree Feb 3, 2022
6d87b1d
nit
chrismaree Feb 3, 2022
b03f37e
nit
chrismaree Feb 3, 2022
f45389b
nit
chrismaree Feb 3, 2022
b33a77b
nit
chrismaree Feb 3, 2022
12d0811
nit
chrismaree Feb 3, 2022
b02f2a8
nit
chrismaree Feb 3, 2022
03c4d00
nit
chrismaree Feb 3, 2022
b6cca33
nit
chrismaree Feb 4, 2022
d4d27fc
nit
chrismaree Feb 4, 2022
1480614
WIP
chrismaree Feb 4, 2022
804bb41
nit
chrismaree Feb 4, 2022
ce3f29c
nit
chrismaree Feb 4, 2022
4d2e566
WIP
chrismaree Feb 4, 2022
3736565
nit
chrismaree Feb 4, 2022
bea5162
nit
chrismaree Feb 4, 2022
8c1278b
nit
chrismaree Feb 4, 2022
da8b83d
nit
chrismaree Feb 4, 2022
96cc1a4
nit
chrismaree Feb 4, 2022
66a75f2
nit
chrismaree Feb 4, 2022
e0c3307
nit
chrismaree Feb 4, 2022
ad372f6
nit
chrismaree Feb 4, 2022
4c986c4
nit
chrismaree Feb 5, 2022
ff1f10c
nit
chrismaree Feb 5, 2022
c46e94f
feat(slack-config): Add sync method and liquidity utilization
chrismaree Feb 8, 2022
b884d2f
nit
chrismaree Feb 8, 2022
505ee77
Merge branch 'master' into chrismaree/fee-tracking-2
chrismaree Feb 8, 2022
986df76
Delete settings.json
chrismaree Feb 8, 2022
2238088
nit
chrismaree Feb 8, 2022
5bd1473
nit
chrismaree Feb 8, 2022
408223c
nit
chrismaree Feb 8, 2022
24928d5
nit
chrismaree Feb 8, 2022
3d74944
nit
chrismaree Feb 8, 2022
08281ac
nit
chrismaree Feb 8, 2022
0a79da6
nit
chrismaree Feb 8, 2022
13b022b
nit
chrismaree Feb 8, 2022
edce0cc
nit
chrismaree Feb 8, 2022
9a75854
nit
chrismaree Feb 9, 2022
3ded224
nit
chrismaree Feb 9, 2022
d6e55d8
nit
chrismaree Feb 9, 2022
112b322
nit
chrismaree Feb 9, 2022
485e9bd
Merge branch 'master' into chrismaree/test-adapters
chrismaree Feb 9, 2022
7becd28
nit
chrismaree Feb 9, 2022
b2656c7
nit
chrismaree Feb 9, 2022
fe81d40
nit
chrismaree Feb 9, 2022
58c6e67
nit
chrismaree Feb 10, 2022
ac8ad23
nit
chrismaree Feb 10, 2022
cc4513e
Merge branch 'master' into chrismaree/arbitrum-l1-adapter
chrismaree Feb 10, 2022
cb35588
feat: Add arbitrum adapter
chrismaree Feb 11, 2022
52d3ce5
nit
chrismaree Feb 11, 2022
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
130 changes: 130 additions & 0 deletions contracts/chain-adapters/Arbitrum_Adapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import "./Base_Adapter.sol";
import "../interfaces/AdapterInterface.sol";
import "../interfaces/WETH9.sol";

import "@uma/core/contracts/common/implementation/Lockable.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface ArbitrumL1InboxLike {
function createRetryableTicket(
address destAddr,
uint256 arbTxCallValue,
uint256 maxSubmissionCost,
address submissionRefundAddress,
address valueRefundAddress,
uint256 maxGas,
uint256 gasPriceBid,
bytes calldata data
) external payable returns (uint256);
}

interface ArbitrumL1ERC20GatewayLike {
function outboundTransfer(
address _token,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes memory);
}

contract Arbitrum_Adapter is Base_Adapter, Lockable {
// Gas limit for immediate L2 execution attempt (can be estimated via NodeInterface.estimateRetryableTicket).
// NodeInterface precompile interface exists at L2 address 0x00000000000000000000000000000000000000C8
uint32 public l2GasLimit = 5_000_000;

// Amount of ETH allocated to pay for the base submission fee. The base submission fee is a parameter unique to
// retryable transactions; the user is charged the base submission fee to cover the storage costs of keeping their
// ticket’s calldata in the retry buffer. (current base submission fee is queryable via
// ArbRetryableTx.getSubmissionPrice). ArbRetryableTicket precompile interface exists at L2 address
// 0x000000000000000000000000000000000000006E.
uint256 public l2MaxSubmissionCost = 0.1e18;

// L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC)
uint256 public l2GasPrice = 10e9; // 10 gWei

// This address on L2 receives extra ETH that is left over after relaying a message via the inbox.
address public l2RefundL2Address;

ArbitrumL1InboxLike public l1Inbox;

ArbitrumL1ERC20GatewayLike public l1ERC20Gateway;

event L2GasLimitSet(uint32 newL2GasLimit);

event L2MaxSubmissionCostSet(uint256 newL2MaxSubmissionCost);

event L2GasPriceSet(uint256 newL2GasPrice);

event L2RefundL2AddressSet(address newL2RefundL2Address);

constructor(
address _hubPool,
ArbitrumL1InboxLike _l1ArbitrumInbox,
ArbitrumL1ERC20GatewayLike _l1ERC20Gateway
) Base_Adapter(_hubPool) {
l1Inbox = _l1ArbitrumInbox;
l1ERC20Gateway = _l1ERC20Gateway;

l2RefundL2Address = owner();
}

function setL2GasLimit(uint32 _l2GasLimit) public onlyOwner {
l2GasLimit = _l2GasLimit;
emit L2GasLimitSet(l2GasLimit);
}

function setL2MaxSubmissionCost(uint256 _l2MaxSubmissionCost) public onlyOwner {
l2MaxSubmissionCost = _l2MaxSubmissionCost;
emit L2MaxSubmissionCostSet(l2MaxSubmissionCost);
}

function setL2GasPrice(uint256 _l2GasPrice) public onlyOwner {
l2GasPrice = _l2GasPrice;
emit L2GasPriceSet(l2GasPrice);
}

function setL2RefundL2Address(address _l2RefundL2Address) public onlyOwner {
l2RefundL2Address = _l2RefundL2Address;
emit L2RefundL2AddressSet(l2RefundL2Address);
}

function relayMessage(address target, bytes memory message) external payable override nonReentrant onlyHubPool {
uint256 requiredL1CallValue = getL1CallValue();
require(address(this).balance >= requiredL1CallValue, "Insufficient ETH balance");
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we'' have an EOA send ETH to this contract prior to cross chain calls, or will the HubPool send ETH over?

Copy link
Member Author

Choose a reason for hiding this comment

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

EOA just drops funds on it I think.

Copy link
Member

Choose a reason for hiding this comment

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

WDYT about making this required L1 call value publicly queryable, for a potential EOA's convenience? We do this with the cross-chain oracle here but it may not be useful for this case if the HubPool deterministically decides when to send over ETH

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, that's a good idea. it will also let us automate when to top up the contract. I'll refactor now.

Copy link
Member Author

Choose a reason for hiding this comment

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

I dont, however, think this needs to be nonReentrantView like your method. that's way overkill IMHO


l1Inbox.createRetryableTicket{ value: requiredL1CallValue }(
target, // destAddr destination L2 contract address
0, // l2CallValue call value for retryable L2 message
l2MaxSubmissionCost, // maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
l2RefundL2Address, // excessFeeRefundAddress maxgas x gasprice - execution cost gets credited here on L2 balance
l2RefundL2Address, // callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
l2GasLimit, // maxGas Max gas deducted from user's L2 balance to cover L2 execution
l2GasPrice, // gasPriceBid price bid for L2 execution
message // data ABI encoded data of L2 message
);

emit MessageRelayed(target, message);
}

function relayTokens(
address l1Token,
address l2Token, // l2Token is unused for Arbitrum.
uint256 amount,
address to
) external payable override nonReentrant onlyHubPool {
l1ERC20Gateway.outboundTransfer(l1Token, to, amount, l2GasLimit, l2GasPrice, "");
emit TokensRelayed(l1Token, l2Token, amount, to);
}

function getL1CallValue() public view returns (uint256) {
return l2MaxSubmissionCost + l2GasPrice * l2GasLimit;
}

receive() external payable {}
}
17 changes: 10 additions & 7 deletions contracts/chain-adapters/Optimism_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ import "../interfaces/WETH9.sol";
import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol";
import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol";

import "@uma/core/contracts/common/implementation/Lockable.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @notice Sends cross chain messages Optimism L2 network.
* @dev This contract's owner should be set to the BridgeAdmin deployed on the same L1 network so that only the
* BridgeAdmin can call cross-chain administrative functions on the L2 SpokePool via this messenger.
* @dev This contract's owner should be set to the some multisig or admin contract. The Owner can simply set the L2 gas
* and the HubPool. The HubPool is the only contract that can relay tokens and messages over the bridge.
*/
contract Optimism_Adapter is Base_Adapter, CrossDomainEnabled {
contract Optimism_Adapter is Base_Adapter, CrossDomainEnabled, Lockable {
uint32 public l2GasLimit = 5_000_000;

WETH9 public l1Weth;

IL1StandardBridge public l1StandardBridge;

event L2GasLimitSet(uint32 newGasLimit);

constructor(
WETH9 _l1Weth,
address _hubPool,
Expand All @@ -34,21 +38,20 @@ contract Optimism_Adapter is Base_Adapter, CrossDomainEnabled {

function setL2GasLimit(uint32 _l2GasLimit) public onlyOwner {
l2GasLimit = _l2GasLimit;
emit L2GasLimitSet(l2GasLimit);
}

function relayMessage(address target, bytes memory message) external payable override onlyHubPool {
function relayMessage(address target, bytes memory message) external payable override nonReentrant onlyHubPool {
sendCrossDomainMessage(target, uint32(l2GasLimit), message);
emit MessageRelayed(target, message);
}

// TODO: we should look into using delegate call as this current implementation assumes the caller transfers the
// tokens first to this contract. This will work with eth based transfers and for now we'll ignore it.
function relayTokens(
address l1Token,
address l2Token,
uint256 amount,
address to
) external payable override onlyHubPool {
) external payable override nonReentrant onlyHubPool {
// If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge.
if (l1Token == address(l1Weth)) {
l1Weth.withdraw(amount);
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"@openzeppelin/contracts": "^4.4.2",
"@uma/common": "^2.17.0",
"@uma/contracts-node": "^0.2.0",
"@uma/core": "^2.24.0"
"@uma/core": "^2.24.0",
"arb-bridge-eth": "^0.7.4",
"arb-bridge-peripherals": "^1.0.5"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions test/MerkleLib.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ export function buildPoolRebalanceLeafs(
});
}

export async function constructSingleChainTree(token: Contract, scalingSize = 1, _repaymentChainId = repaymentChainId) {
export async function constructSingleChainTree(token: Contract, scalingSize = 1, repaymentChain = repaymentChainId) {
const tokensSendToL2 = toBNWei(100 * scalingSize);
const realizedLpFees = toBNWei(10 * scalingSize);
const leafs = buildPoolRebalanceLeafs(
[_repaymentChainId], // repayment chain. In this test we only want to send one token to one chain.
[repaymentChain], // repayment chain. In this test we only want to send one token to one chain.
[token], // l1Token. We will only be sending 1 token to one chain.
[[realizedLpFees]], // bundleLpFees.
[[tokensSendToL2]], // netSendAmounts.
Expand Down
105 changes: 105 additions & 0 deletions test/chain-adapters/Arbitrum_Adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as consts from "../constants";
import { ethers, expect, Contract, FakeContract, SignerWithAddress, createFake, toWei } from "../utils";
import { getContractFactory, seedWallet } from "../utils";
import { hubPoolFixture, enableTokensForLP } from "../HubPool.Fixture";
import { constructSingleChainTree } from "../MerkleLib.utils";

let hubPool: Contract, arbitrumAdapter: Contract, weth: Contract, dai: Contract, timer: Contract, mockSpoke: Contract;
let l2Weth: string, l2Dai: string;
let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress;
let l1ERC20Gateway: FakeContract, l1Inbox: FakeContract;

const arbitrumChainId = 42161;

describe("Arbitrum Chain Adapter", function () {
beforeEach(async function () {
[owner, dataWorker, liquidityProvider] = await ethers.getSigners();
({ weth, dai, l2Weth, l2Dai, hubPool, mockSpoke, timer } = await hubPoolFixture());
await seedWallet(dataWorker, [dai], weth, consts.amountToLp);
await seedWallet(liquidityProvider, [dai], weth, consts.amountToLp.mul(10));

await enableTokensForLP(owner, hubPool, weth, [weth, dai]);
await weth.connect(liquidityProvider).approve(hubPool.address, consts.amountToLp);
await hubPool.connect(liquidityProvider).addLiquidity(weth.address, consts.amountToLp);
await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10));
await dai.connect(liquidityProvider).approve(hubPool.address, consts.amountToLp);
await hubPool.connect(liquidityProvider).addLiquidity(dai.address, consts.amountToLp);
await dai.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10));

l1Inbox = await createFake("Inbox");
l1ERC20Gateway = await createFake("TokenGateway");

arbitrumAdapter = await (
await getContractFactory("Arbitrum_Adapter", owner)
).deploy(hubPool.address, l1Inbox.address, l1ERC20Gateway.address);

// Seed the Arbitrum adapter with some funds so it can send L1->L2 messages.
await liquidityProvider.sendTransaction({ to: arbitrumAdapter.address, value: toWei("1") });

await hubPool.setCrossChainContracts(arbitrumChainId, arbitrumAdapter.address, mockSpoke.address);

await hubPool.whitelistRoute(arbitrumChainId, weth.address, l2Weth);

await hubPool.whitelistRoute(arbitrumChainId, dai.address, l2Dai);
});

it("Only owner can set l2GasValues", async function () {
expect(await arbitrumAdapter.callStatic.l2GasLimit()).to.equal(consts.sampleL2Gas);
await expect(arbitrumAdapter.connect(liquidityProvider).setL2GasLimit(consts.sampleL2Gas + 1)).to.be.reverted;
await arbitrumAdapter.connect(owner).setL2GasLimit(consts.sampleL2Gas + 1);
expect(await arbitrumAdapter.callStatic.l2GasLimit()).to.equal(consts.sampleL2Gas + 1);
});

it("Only owner can set l2MaxSubmissionCost", async function () {
expect(await arbitrumAdapter.callStatic.l2MaxSubmissionCost()).to.equal(consts.sampleL2MaxSubmissionCost);
await expect(arbitrumAdapter.connect(liquidityProvider).setL2MaxSubmissionCost(consts.sampleL2Gas + 1)).to.be
.reverted;
await arbitrumAdapter.connect(owner).setL2MaxSubmissionCost(consts.sampleL2Gas + 1);
expect(await arbitrumAdapter.callStatic.l2MaxSubmissionCost()).to.equal(consts.sampleL2Gas + 1);
});

it("Only owner can set l2GasPrice", async function () {
expect(await arbitrumAdapter.callStatic.l2GasPrice()).to.equal(consts.sampleL2GasPrice);
await expect(arbitrumAdapter.connect(liquidityProvider).setL2GasPrice(consts.sampleL2Gas + 1)).to.be.reverted;
await arbitrumAdapter.connect(owner).setL2GasPrice(consts.sampleL2Gas + 1);
expect(await arbitrumAdapter.callStatic.l2GasPrice()).to.equal(consts.sampleL2Gas + 1);
});

it("Only owner can set l2RefundL2Address", async function () {
expect(await arbitrumAdapter.callStatic.l2RefundL2Address()).to.equal(owner.address);
await expect(arbitrumAdapter.connect(liquidityProvider).setL2RefundL2Address(liquidityProvider.address)).to.be
.reverted;
await arbitrumAdapter.connect(owner).setL2RefundL2Address(liquidityProvider.address);
expect(await arbitrumAdapter.callStatic.l2RefundL2Address()).to.equal(liquidityProvider.address);
});
it("Correctly calls appropriate arbitrum bridge functions when making ERC20 cross chain calls", async function () {
// Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle
// and check that at it's finalization the L2 bridge contracts are called as expected.
const { leafs, tree, tokensSendToL2 } = await constructSingleChainTree(dai, 1, arbitrumChainId);
await hubPool.connect(dataWorker).initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot);
await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness);
await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0]));
// The correct functions should have been called on the arbitrum contracts.
expect(l1ERC20Gateway.outboundTransfer).to.have.been.calledOnce; // One token transfer over the canonical bridge.

expect(l1ERC20Gateway.outboundTransfer).to.have.been.calledWith(
dai.address,
mockSpoke.address,
tokensSendToL2,
consts.sampleL2Gas,
consts.sampleL2GasPrice,
"0x"
);
expect(l1Inbox.createRetryableTicket).to.have.been.calledOnce; // only 1 L1->L2 message sent.
expect(l1Inbox.createRetryableTicket).to.have.been.calledWith(
mockSpoke.address,
0,
consts.sampleL2MaxSubmissionCost,
owner.address,
owner.address,
consts.sampleL2Gas,
consts.sampleL2GasPrice,
mockSpoke.interface.encodeFunctionData("initializeRelayerRefund", [consts.mockTreeRoot])
);
});
});
4 changes: 4 additions & 0 deletions test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ export const amountToReturn = toWei("1");
export const mockTreeRoot = createRandomBytes32();

export const sampleL2Gas = 5000000;

export const sampleL2MaxSubmissionCost = toWei("0.1");

export const sampleL2GasPrice = 10e9; // 10 gWei
Loading