Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Refactor library usage #2055

Merged
merged 44 commits into from
Aug 16, 2019
Merged

Refactor library usage #2055

merged 44 commits into from
Aug 16, 2019

Conversation

abandeali1
Copy link
Member

@abandeali1 abandeali1 commented Aug 11, 2019

Description

  • Refactors LibMath, LibFillResults, LibOrder, LibZeroExTransaction, and LibSafeMath to be actual libraries. Note that the old SafeMath still exists so that contracts that weren't yet refactored don't break. This can be removed in a separate PR.
  • Simplifies LibEIP712ExchangeDomain to only calculate the domain hash in its constructor and nothing else.
  • Moves calculateFillResults and calculateMatchedFillResults to LibFillResults. All functions in LibFillResults were also refactored to be pure. This refactor should probably be one of the focus areas of this review.
  • Removes all selector constants in favor of the IExchange(address(0)).fillOrder.selector pattern.
  • Refactors LibEIP712.hashEIP712Domain to use assembly. This was actually written pre-vuln, but might be a good discussion to have for all of the hashing functions.
  • Refactors contracts-gen to accept a compiler.json with no specified contracts. In this case, boilerplate for all contracts in the package will be generated. The contracts:gen command is also added into the build command for each package, so it no longer needs to be explicitly called.
  • Removes the _ prefix from internal function names in actual libraries.
  • Moves LibExchangeRichErrors to the exchange-libs package.
  • Random cleanup.

Testing instructions

Types of changes

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@coveralls
Copy link

coveralls commented Aug 12, 2019

Coverage Status

Coverage remained the same at 78.686% when pulling 8402d21 on feat/3.0/optimizeConstants into 434d027 on 3.0.

@abandeali1 abandeali1 changed the title [WIP] Refactor library usage Refactor library usage Aug 12, 2019
Copy link
Contributor

@jalextowle jalextowle left a comment

Choose a reason for hiding this comment

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

Awesome job on this one! I've got a couple of questions and nits, but after that it looks good to go.

selector == FILL_ORDER_SELECTOR ||
selector == FILL_ORDER_NO_THROW_SELECTOR ||
selector == FILL_OR_KILL_ORDER_SELECTOR
selector == IExchange(address(0)).fillOrder.selector ||
Copy link
Contributor

Choose a reason for hiding this comment

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

This change is 🔥. I'm so glad we're getting rid of LibExchangeSelectors.

tokenId
);

(bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if it would make sense to make a staticcall library that you could use to abstract away some of this logic. For example, this staticcall could be written as:

balance = tokenAddress.staticcallReturnsAddress(ownerOfCalldata) == ownerAddress ? 1 : 0;

This is probably out of scope for this PR, but I think that it would be a good thing to add to the backlog.

transferableMakerFeeAssetAmount,
makerFee,
takerAssetAmount
);
transferableTakerAssetAmount = _min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount);
transferableTakerAssetAmount = LibSafeMath.min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use transferableMakerToTakerAmount.min256(transferableMakerFeeToTakerAmount)?

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 felt like that was less readable, but I could go either way.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have a huge preference either way.

}
}

// `fillableTakerAssetAmount` is the lower of the order's remaining `takerAssetAmount` and the `transferableTakerAssetAmount`
fillableTakerAssetAmount = _min256(
_safeSub(takerAssetAmount, orderInfo.orderTakerAssetFilledAmount),
fillableTakerAssetAmount = LibSafeMath.min256(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

@@ -171,7 +172,7 @@ contract OrderValidationUtils is
returns (uint256 transferableAssetAmount)
{
(uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData);
transferableAssetAmount = _min256(balance, allowance);
transferableAssetAmount = LibSafeMath.min256(balance, allowance);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

uint256 remainingTakerAssetAmount = _safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount);
uint256 takerAssetFilledAmount = _min256(takerAssetFillAmount, remainingTakerAssetAmount);
uint256 remainingTakerAssetAmount = order.takerAssetAmount.safeSub(orderInfo.orderTakerAssetFilledAmount);
uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as earlier.

));
bytes32 schemaHash = _EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH;

// Assembly for more efficient computing:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

/// @param fillResults1 The first FillResults.
/// @param fillResults2 The second FillResults.
/// @return The sum of both fill results.
function addFillResults(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a fan of this not being a destructive function. I think that we should make this the norm (if it isn't already).

// If the left maker can buy exactly as much as the right maker can sell, then both orders are fully filled.
if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) {
// Case 1: Right order is fully filled
matchedFillResults = _calculateCompleteRightFill(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above. Really glad this is non-destructive now.

// leftTakerAssetAmountRemaining == rightMakerAssetAmountRemaining
// Case 3: Both orders are fully filled. Technically, this could be captured by the above cases, but
// this calculation will be more precise since it does not include rounding.
matchedFillResults = _calculateCompleteFillBoth(
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

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

This is solid! I'm happy we were able to fit this refactor into 3.0 (~‾▿‾)~

pragma solidity ^0.5.9;


contract LibExchangeSelectors {
Copy link
Contributor

Choose a reason for hiding this comment

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

RIP level 💯

import "../src/LibFillResults.sol";


contract TestLibFillResults {
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a lot of these public-to-internal wrappers now. It could be worth looking into auto-generating them. This would also alleviate some of the code bloat. I've created a task on our backlog to look into this.

import "./LibSafeMathRichErrors.sol";


library LibSafeMath {
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏

// uint256(verifyingContractAddress)
// ))

assembly {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks good to me ~ do you know roughly how much gas this saves?

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 don't think it's super significant, maybe 1000 or so. Also note that this function only gets called once in the Exchange contract. I mostly did this for consistency pre-vuln since all of the other hashing is done in assembly, but it would probably be a good time to formalize what we want to do with all of the hashing as a whole.

Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

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

Yup, these are all much cleaner changes! 💯
Some small requests then probably g2g after rebasing against 3.0.
Not approving yet because I wanna take another look after the rebase.

@@ -35,7 +35,7 @@
"generate-exchange-selectors": "node lib/scripts/generate-exchange-selectors.js ../../../exchange/generated-artifacts/Exchange.json ./contracts/src/LibExchangeSelectors.sol"
Copy link
Contributor

Choose a reason for hiding this comment

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

We can get rid of this and the associated script now. 🎉

contract LibFillResults is
SafeMath
{
library LibFillResults {
Copy link
Contributor

Choose a reason for hiding this comment

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

Digging this refactor.

public
view
function getOrderHash(Order memory order, bytes32 eip712ExchangeDomainHash)
internal
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I wonder if we should expose a getOrderHash() function in MixinExchangeCore to make up for this being turned into an internal library. getOrderInfo() might be overkill for some users.

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 discussed this with Fabio and we agreed that we can just put this in the DevUtils contract. Do you think that is sufficient?

public
view
function getTransactionHash(ZeroExTransaction memory transaction, bytes32 eip712ExchangeDomainHash)
internal
Copy link
Contributor

Choose a reason for hiding this comment

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

Same. Consider exposing getTransactionHash() in MixinTransactions.

return orderHash;
}

/// @dev Calculates EIP712 hash of the order.
/// @param order The order structure.
/// @return EIP712 hash of the order.
function _hashOrder(Order memory order)
function hashOrder(Order memory order)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just rename this to getHash() now since order.hashOrder() is stuttering.

env.provider,
env.txDefaults,
);
chainId = await providerUtils.getChainIdAsync(env.provider);
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, getting a legitimate chainId doesn't seem to matter?


// Compute proportional fill amounts
fillResults = _calculateFillResults(order, takerAssetFilledAmount);
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibEIP1271.sol";


interface ISimplifiedExchange {
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏

@@ -1,57 +0,0 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't even know this contract existed.

}
});
compilerJSON.contracts = _.sortBy(compilerJSON.contracts);
if (compilerJSON.contracts !== undefined && compilerJSON.contracts !== ALL_CONTRACTS_IDENTIFIER) {
Copy link
Contributor

Choose a reason for hiding this comment

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

NOICE! 👍

Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

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

Just one small nit then g2g! 👍 💃

makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
};
const EMPTY_MATCHED_FILL_RESULTS: MatchedFillResults = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move the matched fill results stuff closer to the tests that use them?

@abandeali1 abandeali1 merged commit 4c78b7d into 3.0 Aug 16, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants