Document noThrow wrapper and correct fixed array offset

This commit is contained in:
Remco Bloemen
2018-02-26 13:01:51 -08:00
committed by Amir Bandeali
parent 6d7097eed5
commit 58c5e800d0

View File

@@ -61,53 +61,95 @@ contract MixinWrapperFunctions is
public
returns (bool success, uint256 takerTokenFilledAmount)
{
bytes4 FILL_ORDER_FUNCTION_SIGNATURE = bytes4(keccak256("fillOrder(address[5],uint256[6],uint256,uint8,bytes32,bytes32)"));
// We need to call MExchangeCore.fillOrder using a delegatecall in
// assembly so that we can intercept a call that throws. For this, we
// need the input encoded in memory in the Ethereum ABI format [1].
//
// | Offset | Length | Contents |
// |--------|--------|------------------------------|
// | 0 | 4 | function selector |
// | 4 | 160 | address[5] orderAddresses |
// | 164 | 192 | uint256[6] orderValues |
// | 356 | 32 | uint256 takerTokenFillAmount |
// | 388 | 32 | len(signature) |
// | 420 | (1) | signature |
// | (2) | (3) | padding (zero) |
// | (4) | | end of input |
//
// (1): len(signature)
// (2): 420 + len(signature)
// (3): (32 - len(signature)) mod 32
// (4): 420 + len(signature) + (32 - len(signature)) mod 32
//
// [1]: https://solidity.readthedocs.io/en/develop/abi-spec.html
// Input size is padded to a 4 + n * 32 byte boundary
// TODO: Construct the input array using readable Solidity instead
// of assembly.
uint256 mask = 0x1F;
uint256 inputSize = 388 + (signature.length + mask) & ~mask;
// Allocate memory for input
uint256 signatureLength = signature.length;
uint256 paddingLength = (32 - signatureLength) % 32
uint256 inputSize = 420 + signatureLength + paddingLength;
bytes memory input = new bytes(inputSize);
// The in memory layout of `bytes memory input` starts with
// `uint256 length`, the content of the byte array starts at
// offset 32 from the pointer. We need assembly to access the
// raw pointer value of `input`.
// TODO: I can not find an official source for this.
uint256 start;
assembly {
start := add(input, 32)
}
// Write function signature
// We use assembly to write four bytes at a time (actually 32,
// but we are only overwriting uninitialized data). A `bytes4`
// is stored by value as 256-bit and right-padded with zeros.
bytes4 FILL_ORDER_FUNCTION_SIGNATURE = bytes4(keccak256("fillOrder(address[5],uint256[6],uint256,uint8,bytes32,bytes32)"));
assembly {
mstore(start, FILL_ORDER_FUNCTION_SIGNATURE);
}
// Write orderAddresses, orderValues, takerTokenFillAmount
// and len(signature)
// It is done in assembly so we can write 32 bytes at a time. In
// solidity we would have to copy one byte at a time.
// Fixed size arrays do not have the `uint256 length` prefix that
// dynamic arrays have [citation needed].
assembly {
mstore(add(start, 4), orderAddresses) // maker
mstore(add(start, 36), add(orderAddresses, 32)) // taker
mstore(add(start, 68), add(orderAddresses, 64)) // makerToken
mstore(add(start, 100), add(orderAddresses, 96)) // takerToken
mstore(add(start, 132), add(orderAddresses, 128)) // feeRecipient
mstore(add(start, 164), orderValues) // makerTokenAmount
mstore(add(start, 196), add(orderValues, 32)) // takerTokenAmount
mstore(add(start, 228), add(orderValues, 64)) // makerFee
mstore(add(start, 260), add(orderValues, 96)) // takerFee
mstore(add(start, 292), add(orderValues, 128)) // expiration
mstore(add(start, 356), takerTokenFillAmount)
mstore(add(start, 388), signatureLength)
}
// Write signature contents and padding one byte at a time
for (uint256 i = 0; i < signatureLength; i++) {
input[420 + i] = signature[i];
}
for (uint256 i = 0; i < paddingLength; i++) {
input[420 + signatureLength + i] = 0x00;
}
// Call the function
assembly {
let x := mload(0x40) // free memory pointer
mstore(x, FILL_ORDER_FUNCTION_SIGNATURE)
// first 32 bytes of a dynamic in-memory array contains length
mstore(add(x, 4), add(orderAddresses, 32)) // maker
mstore(add(x, 36), add(orderAddresses, 64)) // taker
mstore(add(x, 68), add(orderAddresses, 96)) // makerToken
mstore(add(x, 100), add(orderAddresses, 128)) // takerToken
mstore(add(x, 132), add(orderAddresses, 160)) // feeRecipient
mstore(add(x, 164), add(orderValues, 32)) // makerTokenAmount
mstore(add(x, 196), add(orderValues, 64)) // takerTokenAmount
mstore(add(x, 228), add(orderValues, 96)) // makerFee
mstore(add(x, 260), add(orderValues, 128)) // takerFee
mstore(add(x, 292), add(orderValues, 160)) // expirationTimestampInSec
mstore(add(x, 324), add(orderValues, 192)) // salt
mstore(add(x, 356), takerTokenFillAmount)
for {
let src := signature
let dst := add(x, 388)
let end := add(add(src, mload(signature)), 32)
} lt(src, end) {
src := add(src, 32)
dst := add(dst, 32)
} {
mstore(dst, mload(src))
// TODO: zero the padding
}
success := delegatecall(
gas, // TODO: don't send all gas, save some for returning in case of throw
gas, // If fillOrder `revert()`s, we recover unused gas
address, // call this contract
x, // inputs start at x
inputSize, // input are size
x, // store output over input
start, // pointer to start of input
inputSize, // length of input
start, // store output over input
32 // output is 32 bytes
)
takerTokenFilledAmount := mload(x)
// Read output value (uint256 takerTokenFilledAmount)
takerTokenFilledAmount := mload(start)
}
return (success, takerTokenFilledAmount);
}