In @0x/contracts-exchange: More efficient revert string extraction in MixinAssetProxyDispatcher.sol

This commit is contained in:
Lawrence Forman
2019-04-11 14:13:51 -04:00
committed by Amir Bandeali
parent 440c4fe9b9
commit 8194e3d3c5
3 changed files with 25 additions and 31 deletions

View File

@@ -113,7 +113,7 @@ contract MixinAssetProxyDispatcher is
// Whether the AssetProxy transfer succeeded.
bool didSucceed;
// On failure, the revert message returned by the asset proxy.
bytes memory revertMessage;
string memory revertMessage;
// We construct calldata for the `assetProxy.transferFrom` ABI.
// The layout of this calldata is in the table below.
@@ -134,8 +134,6 @@ contract MixinAssetProxyDispatcher is
/////// Setup State ///////
// `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr).
let cdStart := mload(64)
// We reserve 288 bytes from `cdStart` because we'll reuse it later for return data.
mstore(64, add(cdStart, 288))
// `dataAreaLength` is the total number of words needed to store `assetData`
// As-per the ABI spec, this value is padded up to the nearest multiple of 32,
// and includes 32-bytes for length.
@@ -143,7 +141,6 @@ contract MixinAssetProxyDispatcher is
// `cdEnd` is the end of the calldata for `assetProxy.transferFrom`.
let cdEnd := add(cdStart, add(132, dataAreaLength))
/////// Setup Header Area ///////
// This area holds the 4-byte `transferFromSelector`.
// bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4
@@ -177,40 +174,35 @@ contract MixinAssetProxyDispatcher is
0, // don't send any ETH
cdStart, // pointer to start of input
sub(cdEnd, cdStart), // length of input
cdStart, // write output over input
288 // reserve 288 bytes for output
0, // don't store the returndata
0 // don't store the returndata
)
if iszero(didSucceed) { // Call reverted.
// Attempt to to decode standard revert messages with the
// signature `Error(string)` so we can upgrade them to
// a rich revert.
// The asset proxies always revert with a standard string error,
// which have the signature `Error(string)` and are ABI encoded
// the same way as calldata for a function call is.
// We will extract the string payload from this to upgrade it
// to a rich revert.
// Standard revert errors have the following memory layout:
// Asset proxy revert errors have the following memory layout:
// | Area | Offset | Length | Contents |
// --------------------------------------------------------------------------------
// | Selector | 0 | 4 | bytes4 function selector (0x08c379a0) |
// | Params | | | |
// | | 4 | 32 | uint256 offset to data (o) |
// | | 4 | 32 | uint256 offset to data (always 32) |
// | Data | | | |
// | | 4 + o | 32 | uint256 length of data (n |
// | | 32 + o | n | Left-aligned message bytes |
// | | 36 | 32 | uint256 length of data (n) |
// | | 68 | n | Left-aligned message bytes |
// Make sure the return value is long enough.
if gt(returndatasize(), 67) {
// Check that the selector matches for a standard revert error.
let selector := and(mload(sub(cdStart, 28)), 0xffffffff)
cdStart := add(cdStart, 4)
if eq(selector, 0x08c379a0) {
// Set revertMessage to the start of the reason string.
revertMessage := add(cdStart, mload(cdStart))
// Truncate the data length if it's larger than our buffer
// size (288 - 32 = 256)
if gt(mload(revertMessage), 256) {
mstore(revertMessage, 256)
}
}
}
// Get the size of the string data/payload.
let revertDataSize := sub(returndatasize(), 36)
// We need to move the free memory pointer because we
// still have solidity logic that executes after this assembly.
mstore(64, add(cdStart, revertDataSize))
// Copy the revert string data.
returndatacopy(cdStart, 36, revertDataSize)
revertMessage := cdStart
}
}
@@ -218,7 +210,7 @@ contract MixinAssetProxyDispatcher is
rrevert(AssetProxyTransferError(
orderHash,
assetData,
string(revertMessage)
revertMessage
));
}
}

View File

@@ -54,7 +54,7 @@ const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
// tslint:disable:no-unnecessary-type-assertion
describe('Exchange core', () => {
describe.only('Exchange core', () => {
let chainId: number;
let makerAddress: string;
let owner: string;
@@ -700,6 +700,7 @@ describe('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.makerAssetData,
RevertReason.TransferFailed,
);
const tx = exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
return expect(tx).to.revertWith(expectedError);
@@ -726,6 +727,7 @@ describe('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.takerAssetData,
RevertReason.TransferFailed,
);
const tx = exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
return expect(tx).to.revertWith(expectedError);

View File

@@ -954,7 +954,7 @@ function validationErrorToRevertError(order: Order, reason: RevertReason): Rever
case RevertReason.InvalidFillPrice:
return new ExchangeRevertErrors.FillError(orderHash, ExchangeRevertErrors.FillErrorCode.InvalidFillPrice);
case RevertReason.TransferFailed:
return new ExchangeRevertErrors.AssetProxyTransferError(orderHash, undefined, 'TRANSFER_FAILED');
return new ExchangeRevertErrors.AssetProxyTransferError(orderHash, undefined, RevertReason.TransferFailed);
default:
return new StringRevertError(reason);
}