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

[3.0] Exchange Rich Reverts (once more, with feeling!) #1761

Merged

Conversation

dorothy-zbornak
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak commented Apr 5, 2019

Description

sorpresa

This is a complete overhaul and more holistic implementation of my initial proof-of-concept #1688. Kindly refer to that PR for background on the issue, encoding details, and to mock my rookie naiveté.

Summary

This PR introduces the tools for raising, consuming, and testing rich revert errors from smart contracts. It introduces a new idiom for testing contract reverts (rich or plain) via Chai extensions. It also upgrades all errors thrown by the Exchange contract into said rich reverts.

Notable Changes

  • Smart Contracts

  • Tooling

    • New RevertError base class in @0x/utils:
      • Encapsulates both a plain revert (StringRevertError) and rich reverts.
      • Flexible equality testing between two instances (only mutually defined parameters are tested).
      • Pluggable registry of known types/signatures.
      • Decoding of ABI-encoded revert errors into a concrete instance.
      • Encoding of instances to ABI-encoded revert errors, which is useful in testing of TransactionExecutionErrors which carry an ABI-encoded payload of the original error.
      • Extends Error for backwards compatiblity with existing tests.
    • Concrete RevertError types for the Exchange in @0x/order-utils.ExchangeRevertErrors.
    • Robust Chai extensions in @0x/dev-utils:
      • Overrides equal() to and become() to support comparisons between RevertError types.
      • Exposes a new method/predicate, revertWith(), for testing both transaction and call promises that should revert. It's also backwards compatible with regular string reverts, such as those in @0x/types.RevertReason.
      • Should work against ganache and geth (geth is iffy until we get our test node back up 🤷‍♀️).
      • Supplants the clunky expectContractCallFailedAsync() and expectTransactionFailedAsync() functions from @0x/contracts-test-utils with a nice Chai expression: return expect(tx).to.revertWith(...).
    • @0x/contracts-test-utils and others now inherit their chaiSetup from @0x/dev-utils.chaiSetup so the new Chai extensions are available just about everywhere.
    • Augments @0x/base-contract to automatically decode and throw RevertErrors.

Testing instructions

Affected contract tests have been updated/converted to handle rich reverts conform to the new testing idiom. Specifically, these packages are:

  • @0x/contracts-exchange
  • @0x/contracts-extensions

Tests for RevertErrors and the new Chai extensions have been added to:

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

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.

Addresses 0xProject/ZEIPs/issues/32

Fix compilation issues in `exchange`.
Add rich reverts chai helper tests to `dev-utils`
Rename `StandardError` to `StringRevertError`.
Rename `RichRevertAbi` to `RevertErrorAbi`.
Make `RevertError` extend `Error` so it can be thrown.
Add `RevertError` tests.
Tweak `RevertError` coercion in chai helper.
Add more `RevertError` chai helper tests.
…and throw `RevertError`s.

Remove no longer necessary dependency on `ethers.js` in `@0x/base-contract`.
…de not passing all arguments to previous handler.

In `@0x/dev-utils` add more `RevertError` chai helper tests for backwards compatibility with `rejectedWith`.
In `@0x/dev-utils` instead of overriding `rejectedWith`, add a new method `revertWith`.
In `@0x/dev-utils` clean up the code for the `RevertError` chai helper.
…te file from `chai_setup.ts`.

In `@0x/dev-utils`: Add chai support for ganache and geth transaction reverts.
@fabioberger
Copy link
Contributor

Approved the Typescript code only.

contracts/exchange/contracts/src/MixinExchangeCore.sol Outdated Show resolved Hide resolved
contracts/exchange/contracts/src/MixinTransactions.sol Outdated Show resolved Hide resolved
contracts/utils/contracts/src/RichErrors.sol Outdated Show resolved Hide resolved
contracts/utils/contracts/src/RichErrors.sol Show resolved Hide resolved
Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

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

This PR is sooooo nice and super clean. There are a few nits before merging, but great work!

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.

Looks awesome and I'm excited to use it! Good to merge after addressing @abandeali1's comments.

…eRevertErrors.SignatureError`.

In `@0x/contracts-exchange`: Add `signerAddress` and `signature` to `SignatureError` reverts.
…ertErrors`.

In `@0x/contracts-exchange`: Add `TransactionSignatureError`, supplanting `TransactionErrorCodes.BAD_SIGNATURE`, and associated test.
@dorothy-zbornak dorothy-zbornak force-pushed the feature/contracts/exchange/3.0/rich-revert-reasons branch from 17cc83d to cb010eb Compare April 12, 2019 16:37
// | | 68 | n | Left-aligned message bytes |

// Get the size of the string data/payload.
let revertDataSize := sub(returndatasize(), 36)
Copy link
Member

Choose a reason for hiding this comment

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

Omitting the first 36 bytes works if we assume that the revert data is always a string, but what if we move to rich revert reasons for asset proxies too? In that case, it's probably better to just include all of the return data and decode client side based off of the function selector.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented here.

}
return isValid;
// Static call the verification function.
(bool didSucceed, bytes memory returnData) = walletAddress.staticcall(callData);
Copy link
Member

Choose a reason for hiding this comment

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

I believe that you can just do IValidator.isValidSignature(...) and it will compile with the staticcall opcode in Solidity ^0.5.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, but don't we want to catch the revert?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can return the revert data, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented here and here.

abandeali1 and others added 4 commits April 12, 2019 13:55
…Error` to `ExchangeRevertErrors`. Update `AssetProxyTransferError` to accept arbitrary `errorData` bytes instead of a `revertReason` string.
…eValidatorError` rich reverts.

In `@0x/contracts-exchange`: Change `AssetProxyTransferError` to accept a `revertData` bytes instead of a `revertReason` string.
In `@0x/contracts-exchange`: Aadd `contracts/test/TestRevertReceiver.sol` for testing that validator/wallet reverts are properly wrapped.
@dorothy-zbornak dorothy-zbornak merged commit 07fe677 into 3.0 Apr 12, 2019
@dekz dekz added this to the v3 development milestone Jun 21, 2019
@dorothy-zbornak dorothy-zbornak deleted the feature/contracts/exchange/3.0/rich-revert-reasons branch August 19, 2019 19:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants