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
2 changes: 1 addition & 1 deletion contracts/HubPool.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//SPDX-License-Identifier: Unlicense
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "@uma/core/contracts/common/implementation/Testable.sol";
Expand Down
105 changes: 105 additions & 0 deletions contracts/MerkleLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";


/**
* @notice Library to help with merkle roots, proofs, and claims.
*/
library MerkleLib {
// TODO: some of these data structures can be moved out if convenient.
// This data structure is used in the settlement process on L1.
// Each PoolRebalance structure is responsible for balancing a single chain's SpokePool across all tokens.
struct PoolRebalance {
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint256 leafId;
// This is used to know which chain to send cross-chain transactions to (and which SpokePool to sent to).
uint256 chainId;

// The following arrays are required to be the same length. They are parallel arrays for the given chainId and should be ordered by the `tokenAddresses` field.
// All whitelisted tokens with nonzero relays on this chain in this bundle in the order of whitelisting.
address[] tokenAddresses;
uint256[] bundleLpFees; // Total LP fee amount per token in this bundle, encompassing all associated bundled relays.

// This array is grouped with the two above, and it represents the amount to send or request back from the
// SpokePool. If positive, the pool will pay the SpokePool. If negative the SpokePool will pay the HubPool.
// There can be arbitrarily complex rebalancing rules defined offchain. This number is only nonzero
// when the rules indicate that a rebalancing action should occur. When a rebalance does not occur,
// runningBalance for this token should change by the total relays - deposits in this bundle. When a rebalance
// does occur, runningBalance should be set to zero for this token and the netSendAmount should be set to the
// previous runningBalance + relays - deposits in this bundle.
int256[] netSendAmount;

// This is only here to be emitted in an event to track a running unpaid balance between the L2 pool and the L1 pool.
// A positive number indicates that the HubPool owes the SpokePool funds. A negative number indicates that the
// SpokePool owes the HubPool funds. See the comment above for the dynamics of this and netSendAmount
int256[] runningBalance;
}

// This leaf is meant to be decoded in the SpokePool in order to pay out individual relayers for this bundle.
struct DestinationDistribution {
Copy link
Member

Choose a reason for hiding this comment

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

there was a bool within this struct at one point to indicate if amountToReturn should be sent. what happened to this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed today, this is no longer needed. We don't need to communicate when the rebalances are happening in this data structure. The netSendAmount in the struct above is the only place where we denote whether money is being sent to the chain or not.

// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint256 leafId;
// Used to verify that this is being decoded on the correct chainId.
uint256 chainId;
// This is the amount to return to the HubPool. This occurs when there is a PoolRebalance netSendAmount that is
// negative. This is just that value inverted.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we want the inverted amount. it might be easier if this is always a int256. a positive number always means L1->L2 flow and a negative number always means L2->L1 flow. dont bake any polarity changes into the variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed today, we would only set this number if we wanted to tell the receiving chain to send tokens back to mainnet. So I made this positive since there is no need to communicate when tokens are being sent to the L2 (aka the opposite sign).

uint256 amountToReturn;
// The associated L2TokenAddress that these claims apply to.
address l2TokenAddress;
// These two arrays must be the same length and are parallel arrays. They should be order by refundAddresses.
// This array designates each address that must be refunded.
address[] refundAddresses;
// This array designates how much each of those addresses should be refunded.
uint256[] refundAmounts;
Copy link
Member

Choose a reason for hiding this comment

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

Does the ordering of this struct matter in terms of packing types together?

Copy link
Contributor Author

@mrice32 mrice32 Jan 25, 2022

Choose a reason for hiding this comment

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

I don't think so:

  1. The only type that's currently <32 bytes is address, which is in an array.
  2. Array elements are padded to 32 bytes even if using abi.encodePacked.

}


/**
* @notice Verifies that a repayment is contained within a merkle root.
* @param root the merkle root.
* @param repayment the repayment struct.
* @param proof the merkle proof.
*/
function verifyRepayment(bytes32 root, PoolRebalance memory repayment, bytes32[] memory proof) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(repayment))) || true; // Run code but set to true.
}

/**
* @notice Verifies that a distribution is contained within a merkle root.
* @param root the merkle root.
* @param distribution the distribution struct.
* @param proof the merkle proof.
*/
function verifyDistribution(bytes32 root, DestinationDistribution memory distribution, bytes32[] memory proof) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(distribution))) || true; // Run code but set to true.
}

// The following functions are primarily copied from
// https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol with minor changes.

/**
* @notice Tests whether a claim is contained within a claimedBitMap mapping.
* @param claimedBitMap a simple uint256 mapping in storage used as a bitmap.
* @param index the index to check in the bitmap.
*/
function isClaimed(mapping(uint256 => uint256) storage claimedBitMap, uint256 index) public view returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}

/**
* @notice Marks an index in a claimedBitMap as claimed.
* @param claimedBitMap a simple uint256 mapping in storage used as a bitmap.
* @param index the index to mark in the bitmap.
*/
function setClaimed(mapping(uint256 => uint256) storage claimedBitMap, uint256 index) public {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}
}
2 changes: 1 addition & 1 deletion contracts/SpokePool.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//SPDX-License-Identifier: Unlicense
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

contract SpokePool {
Expand Down