aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/compiler.json2
-rw-r--r--packages/contracts/package.json2
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol7
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol5
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol9
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyOwner/AssetProxyOwner.sol33
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol2
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol7
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol6
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol21
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol26
-rw-r--r--packages/contracts/src/contracts/current/test/TestAssetProxyOwner/TestAssetProxyOwner.sol56
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol91
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol56
-rw-r--r--packages/contracts/src/contracts/current/test/TestWallet/TestWallet.sol8
-rw-r--r--packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol279
-rw-r--r--packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol142
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol5
-rw-r--r--packages/contracts/src/utils/artifacts.ts4
-rw-r--r--packages/contracts/src/utils/multi_sig_wrapper.ts6
-rw-r--r--packages/contracts/src/utils/types.ts1
-rw-r--r--packages/contracts/test/asset_proxy/authorizable.ts69
-rw-r--r--packages/contracts/test/asset_proxy/decoder.ts1
-rw-r--r--packages/contracts/test/asset_proxy_owner.ts344
-rw-r--r--packages/contracts/test/libraries/lib_bytes.ts301
-rw-r--r--packages/contracts/test/libraries/lib_mem.ts190
26 files changed, 965 insertions, 708 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json
index 38580f4dc..6b7b69caa 100644
--- a/packages/contracts/compiler.json
+++ b/packages/contracts/compiler.json
@@ -30,10 +30,10 @@
"MixinAuthorizable",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
+ "TestAssetProxyOwner",
"TestAssetDataDecoders",
"TestAssetProxyDispatcher",
"TestLibBytes",
- "TestLibMem",
"TestLibs",
"TestSignatureValidator",
"TestValidator",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 3bb66067d..8249b9e0c 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -34,7 +34,7 @@
},
"config": {
"abis":
- "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
+ "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
},
"repository": {
"type": "git",
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
index 8cb4254c5..37c12f861 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
@@ -69,7 +69,7 @@ contract MixinAuthorizable is
);
delete authorized[target];
- for (uint i = 0; i < authorities.length; i++) {
+ for (uint256 i = 0; i < authorities.length; i++) {
if (authorities[i] == target) {
authorities[i] = authorities[authorities.length - 1];
authorities.length -= 1;
@@ -87,8 +87,13 @@ contract MixinAuthorizable is
uint256 index
)
external
+ onlyOwner
{
require(
+ authorized[target],
+ TARGET_NOT_AUTHORIZED
+ );
+ require(
index < authorities.length,
INDEX_OUT_OF_BOUNDS
);
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
index 4af39a00b..0e7f3fc89 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
@@ -24,9 +24,10 @@ import "../../tokens/ERC20Token/IERC20Token.sol";
import "./libs/LibTransferErrors.sol";
contract MixinERC20Transfer is
- LibBytes,
LibTransferErrors
{
+ using LibBytes for bytes;
+
/// @dev Internal version of `transferFrom`.
/// @param assetData Encoded byte array.
/// @param from Address to transfer asset from.
@@ -41,7 +42,7 @@ contract MixinERC20Transfer is
internal
{
// Decode asset data.
- address token = readAddress(assetData, 0);
+ address token = assetData.readAddress(0);
// Transfer tokens.
// We do a raw call so we can check the success separate
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
index 6e3156e8a..944068bbb 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
@@ -24,9 +24,10 @@ import "../../tokens/ERC721Token/ERC721Token.sol";
import "./libs/LibTransferErrors.sol";
contract MixinERC721Transfer is
- LibBytes,
LibTransferErrors
{
+ using LibBytes for bytes;
+
/// @dev Internal version of `transferFrom`.
/// @param assetData Encoded byte array.
/// @param from Address to transfer asset from.
@@ -78,10 +79,10 @@ contract MixinERC721Transfer is
)
{
// Decode asset data.
- token = readAddress(assetData, 0);
- tokenId = readUint256(assetData, 20);
+ token = assetData.readAddress(0);
+ tokenId = assetData.readUint256(20);
if (assetData.length > 52) {
- receiverData = readBytes(assetData, 52);
+ receiverData = assetData.readBytesWithLength(52);
}
return (
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyOwner/AssetProxyOwner.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyOwner/AssetProxyOwner.sol
index 7f5f056b5..eb58b3374 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxyOwner/AssetProxyOwner.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyOwner/AssetProxyOwner.sol
@@ -22,24 +22,24 @@ import "../../multisig/MultiSigWalletWithTimeLock.sol";
import "../../utils/LibBytes/LibBytes.sol";
contract AssetProxyOwner is
- LibBytes,
MultiSigWalletWithTimeLock
{
+ using LibBytes for bytes;
event AssetProxyRegistration(address assetProxyContract, bool isRegistered);
// Mapping of AssetProxy contract address =>
- // if this contract is allowed to call the AssetProxy's removeAuthorizedAddress method without a time lock.
+ // if this contract is allowed to call the AssetProxy's `removeAuthorizedAddressAtIndex` method without a time lock.
mapping (address => bool) public isAssetProxyRegistered;
- bytes4 constant REMOVE_AUTHORIZED_ADDRESS_SELECTOR = bytes4(keccak256("removeAuthorizedAddress(address)"));
+ bytes4 constant REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR = bytes4(keccak256("removeAuthorizedAddressAtIndex(address,uint256)"));
- /// @dev Function will revert if the transaction does not call `removeAuthorizedAddress`
+ /// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex`
/// on an approved AssetProxy contract.
- modifier validRemoveAuthorizedAddressTx(uint256 transactionId) {
+ modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) {
Transaction storage tx = transactions[transactionId];
require(isAssetProxyRegistered[tx.destination]);
- require(isFunctionRemoveAuthorizedAddress(tx.data));
+ require(tx.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR);
_;
}
@@ -66,7 +66,7 @@ contract AssetProxyOwner is
}
/// @dev Registers or deregisters an AssetProxy to be able to execute
- /// removeAuthorizedAddress without a timelock.
+ /// `removeAuthorizedAddressAtIndex` without a timelock.
/// @param assetProxyContract Address of AssetProxy contract.
/// @param isRegistered Status of approval for AssetProxy contract.
function registerAssetProxy(address assetProxyContract, bool isRegistered)
@@ -78,13 +78,13 @@ contract AssetProxyOwner is
AssetProxyRegistration(assetProxyContract, isRegistered);
}
- /// @dev Allows execution of removeAuthorizedAddress without time lock.
+ /// @dev Allows execution of `removeAuthorizedAddressAtIndex` without time lock.
/// @param transactionId Transaction ID.
- function executeRemoveAuthorizedAddress(uint256 transactionId)
+ function executeRemoveAuthorizedAddressAtIndex(uint256 transactionId)
public
notExecuted(transactionId)
fullyConfirmed(transactionId)
- validRemoveAuthorizedAddressTx(transactionId)
+ validRemoveAuthorizedAddressAtIndexTx(transactionId)
{
Transaction storage tx = transactions[transactionId];
tx.executed = true;
@@ -95,17 +95,4 @@ contract AssetProxyOwner is
tx.executed = false;
}
}
-
- /// @dev Compares first 4 bytes of byte array to removeAuthorizedAddress function selector.
- /// @param data Transaction data.
- /// @return Successful if data is a call to removeAuthorizedAddress.
- function isFunctionRemoveAuthorizedAddress(bytes memory data)
- public
- pure
- returns (bool)
- {
- bytes4 first4Bytes = readFirst4(data);
- require(REMOVE_AUTHORIZED_ADDRESS_SELECTOR == first4Bytes);
- return true;
- }
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
index f85019012..b8d6c0722 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
@@ -19,14 +19,12 @@
pragma solidity ^0.4.24;
import "../../utils/Ownable/Ownable.sol";
-import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibExchangeErrors.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "../AssetProxy/interfaces/IAssetProxy.sol";
contract MixinAssetProxyDispatcher is
Ownable,
- LibBytes,
LibExchangeErrors,
MAssetProxyDispatcher
{
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index c227d210f..b207b3e57 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -32,7 +32,6 @@ import "./mixins/MAssetProxyDispatcher.sol";
contract MixinExchangeCore is
LibConstants,
- LibBytes,
LibMath,
LibOrder,
LibFillResults,
@@ -42,6 +41,8 @@ contract MixinExchangeCore is
MSignatureValidator,
MTransactions
{
+ using LibBytes for bytes;
+
// Mapping of orderHash => amount of takerAsset already bought by maker
mapping (bytes32 => uint256) public filled;
@@ -411,8 +412,8 @@ contract MixinExchangeCore is
)
private
{
- uint8 makerAssetProxyId = uint8(popLastByte(order.makerAssetData));
- uint8 takerAssetProxyId = uint8(popLastByte(order.takerAssetData));
+ uint8 makerAssetProxyId = uint8(order.makerAssetData.popLastByte());
+ uint8 takerAssetProxyId = uint8(order.takerAssetData.popLastByte());
bytes memory zrxAssetData = ZRX_ASSET_DATA;
dispatchTransferFrom(
order.makerAssetData,
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
index 82325a29f..e36fcc2c5 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
@@ -27,7 +27,6 @@ import "./mixins/MAssetProxyDispatcher.sol";
contract MixinMatchOrders is
LibConstants,
- LibBytes,
LibMath,
LibExchangeErrors,
MAssetProxyDispatcher,
@@ -35,6 +34,7 @@ contract MixinMatchOrders is
MMatchOrders,
MTransactions
{
+ using LibBytes for bytes;
/// @dev Match two complementary orders that have a profitable spread.
/// Each order is filled at their respective price point. However, the calculations are
@@ -242,8 +242,8 @@ contract MixinMatchOrders is
)
private
{
- uint8 leftMakerAssetProxyId = uint8(popLastByte(leftOrder.makerAssetData));
- uint8 rightMakerAssetProxyId = uint8(popLastByte(rightOrder.makerAssetData));
+ uint8 leftMakerAssetProxyId = uint8(leftOrder.makerAssetData.popLastByte());
+ uint8 rightMakerAssetProxyId = uint8(rightOrder.makerAssetData.popLastByte());
bytes memory zrxAssetData = ZRX_ASSET_DATA;
// Order makers and taker
dispatchTransferFrom(
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
index 881d6e7b3..cbb55bfce 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
@@ -26,11 +26,12 @@ import "./interfaces/IWallet.sol";
import "./interfaces/IValidator.sol";
contract MixinSignatureValidator is
- LibBytes,
LibExchangeErrors,
MSignatureValidator,
MTransactions
{
+ using LibBytes for bytes;
+
// Personal message headers
string constant ETH_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n32";
string constant TREZOR_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n\x20";
@@ -102,7 +103,7 @@ contract MixinSignatureValidator is
);
// Ensure signature is supported
- uint8 signatureTypeRaw = uint8(popLastByte(signature));
+ uint8 signatureTypeRaw = uint8(signature.popLastByte());
require(
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
SIGNATURE_UNSUPPORTED
@@ -144,8 +145,8 @@ contract MixinSignatureValidator is
LENGTH_65_REQUIRED
);
v = uint8(signature[0]);
- r = readBytes32(signature, 1);
- s = readBytes32(signature, 33);
+ r = signature.readBytes32(1);
+ s = signature.readBytes32(33);
recovered = ecrecover(hash, v, r, s);
isValid = signerAddress == recovered;
return isValid;
@@ -157,8 +158,8 @@ contract MixinSignatureValidator is
LENGTH_65_REQUIRED
);
v = uint8(signature[0]);
- r = readBytes32(signature, 1);
- s = readBytes32(signature, 33);
+ r = signature.readBytes32(1);
+ s = signature.readBytes32(33);
recovered = ecrecover(
keccak256(abi.encodePacked(ETH_PERSONAL_MESSAGE, hash)),
v,
@@ -199,7 +200,9 @@ contract MixinSignatureValidator is
// | 0x14 + x | 1 | Signature type is always "\x06" |
} else if (signatureType == SignatureType.Validator) {
// Pop last 20 bytes off of signature byte array.
- address validatorAddress = popLast20Bytes(signature);
+
+ address validatorAddress = signature.popLast20Bytes();
+
// Ensure signer has approved validator.
if (!allowedValidators[signerAddress][validatorAddress]) {
return false;
@@ -230,8 +233,8 @@ contract MixinSignatureValidator is
LENGTH_65_REQUIRED
);
v = uint8(signature[0]);
- r = readBytes32(signature, 1);
- s = readBytes32(signature, 33);
+ r = signature.readBytes32(1);
+ s = signature.readBytes32(33);
recovered = ecrecover(
keccak256(abi.encodePacked(TREZOR_PERSONAL_MESSAGE, hash)),
v,
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
index d3d22d48f..20a4a12df 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
@@ -51,17 +51,27 @@ contract MixinTransactions is
/// @param signerAddress Address of transaction signer.
/// @param data AbiV2 encoded calldata.
/// @return EIP712 hash of the Transaction.
- function hashZeroExTransaction(uint256 salt, address signerAddress, bytes data)
+ function hashZeroExTransaction(
+ uint256 salt,
+ address signerAddress,
+ bytes memory data
+ )
internal
pure
- returns (bytes32)
+ returns (bytes32 result)
{
- return keccak256(abi.encode(
- EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
- salt,
- signerAddress,
- keccak256(data)
- ));
+ bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH;
+ bytes32 dataHash = keccak256(data);
+ assembly {
+ let memPtr := mload(64)
+ mstore(memPtr, schemaHash)
+ mstore(add(memPtr, 32), salt)
+ mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff))
+ mstore(add(memPtr, 96), dataHash)
+ result := keccak256(memPtr, 128)
+ }
+
+ return result;
}
/// @dev Executes an exchange method call in the context of signer.
diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyOwner/TestAssetProxyOwner.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyOwner/TestAssetProxyOwner.sol
new file mode 100644
index 000000000..2abcd17a0
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestAssetProxyOwner/TestAssetProxyOwner.sol
@@ -0,0 +1,56 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../protocol/AssetProxyOwner/AssetProxyOwner.sol";
+
+contract TestAssetProxyOwner is
+ AssetProxyOwner
+{
+ constructor(
+ address[] memory _owners,
+ address[] memory _assetProxyContracts,
+ uint256 _required,
+ uint256 _secondsTimeLocked
+ )
+ public
+ AssetProxyOwner(_owners, _assetProxyContracts, _required, _secondsTimeLocked)
+ {
+ }
+
+ function testValidRemoveAuthorizedAddressAtIndexTx(uint256 id)
+ public
+ validRemoveAuthorizedAddressAtIndexTx(id)
+ returns (bool)
+ {
+ // Do nothing. We expect reverts through the modifier
+ return true;
+ }
+
+ /// @dev Compares first 4 bytes of byte array to `removeAuthorizedAddressAtIndex` function selector.
+ /// @param data Transaction data.
+ /// @return Successful if data is a call to `removeAuthorizedAddressAtIndex`.
+ function isFunctionRemoveAuthorizedAddressAtIndex(bytes memory data)
+ public
+ pure
+ returns (bool)
+ {
+ return data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol
index 6f1898acd..f45faaf36 100644
--- a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol
+++ b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol
@@ -21,9 +21,9 @@ pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
-contract TestLibBytes is
- LibBytes
-{
+contract TestLibBytes {
+
+ using LibBytes for bytes;
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
@@ -33,7 +33,7 @@ contract TestLibBytes is
pure
returns (bytes memory, bytes1 result)
{
- result = popLastByte(b);
+ result = b.popLastByte();
return (b, result);
}
@@ -45,7 +45,7 @@ contract TestLibBytes is
pure
returns (bytes memory, address result)
{
- result = popLast20Bytes(b);
+ result = b.popLast20Bytes();
return (b, result);
}
@@ -53,12 +53,23 @@ contract TestLibBytes is
/// @param lhs First byte array to compare.
/// @param rhs Second byte array to compare.
/// @return True if arrays are the same. False otherwise.
- function publicAreBytesEqual(bytes memory lhs, bytes memory rhs)
+ function publicEquals(bytes memory lhs, bytes memory rhs)
public
pure
returns (bool equal)
{
- equal = areBytesEqual(lhs, rhs);
+ equal = lhs.equals(rhs);
+ return equal;
+ }
+
+ function publicEqualsPop1(bytes memory lhs, bytes memory rhs)
+ public
+ pure
+ returns (bool equal)
+ {
+ lhs.popLastByte();
+ rhs.popLastByte();
+ equal = lhs.equals(rhs);
return equal;
}
@@ -73,7 +84,7 @@ contract TestLibBytes is
pure
returns (bytes memory)
{
- deepCopyBytes(dest, source);
+ LibBytes.deepCopyBytes(dest, source);
return dest;
}
@@ -89,7 +100,7 @@ contract TestLibBytes is
pure
returns (address result)
{
- result = readAddress(b, index);
+ result = b.readAddress(index);
return result;
}
@@ -106,7 +117,7 @@ contract TestLibBytes is
pure
returns (bytes memory)
{
- writeAddress(b, index, input);
+ b.writeAddress(index, input);
return b;
}
@@ -122,7 +133,7 @@ contract TestLibBytes is
pure
returns (bytes32 result)
{
- result = readBytes32(b, index);
+ result = b.readBytes32(index);
return result;
}
@@ -139,7 +150,7 @@ contract TestLibBytes is
pure
returns (bytes memory)
{
- writeBytes32(b, index, input);
+ b.writeBytes32(index, input);
return b;
}
@@ -155,7 +166,7 @@ contract TestLibBytes is
pure
returns (uint256 result)
{
- result = readUint256(b, index);
+ result = b.readUint256(index);
return result;
}
@@ -172,19 +183,23 @@ contract TestLibBytes is
pure
returns (bytes memory)
{
- writeUint256(b, index, input);
+ b.writeUint256(index, input);
return b;
}
- /// @dev Reads the first 4 bytes from a byte array of arbitrary length.
- /// @param b Byte array to read first 4 bytes from.
- /// @return First 4 bytes of data.
- function publicReadFirst4(bytes memory b)
+ /// @dev Reads an unpadded bytes4 value from a position in a byte array.
+ /// @param b Byte array containing a bytes4 value.
+ /// @param index Index in byte array of bytes4 value.
+ /// @return bytes4 value from byte array.
+ function publicReadBytes4(
+ bytes memory b,
+ uint256 index
+ )
public
pure
returns (bytes4 result)
{
- result = readFirst4(b);
+ result = b.readBytes4(index);
return result;
}
@@ -192,7 +207,7 @@ contract TestLibBytes is
/// @param b Byte array containing nested bytes.
/// @param index Index of nested bytes.
/// @return result Nested bytes.
- function publicReadBytes(
+ function publicReadBytesWithLength(
bytes memory b,
uint256 index
)
@@ -200,7 +215,7 @@ contract TestLibBytes is
pure
returns (bytes memory result)
{
- result = readBytes(b, index);
+ result = b.readBytesWithLength(index);
return result;
}
@@ -209,7 +224,7 @@ contract TestLibBytes is
/// @param index Index in byte array of <input>.
/// @param input bytes to insert.
/// @return b Updated input byte array
- function publicWriteBytes(
+ function publicWriteBytesWithLength(
bytes memory b,
uint256 index,
bytes memory input
@@ -218,7 +233,37 @@ contract TestLibBytes is
pure
returns (bytes memory)
{
- writeBytes(b, index, input);
+ b.writeBytesWithLength(index, input);
return b;
}
+
+ /// @dev Copies a block of memory from one location to another.
+ /// @param mem Memory contents we want to apply memCopy to
+ /// @param dest Destination offset into <mem>.
+ /// @param source Source offset into <mem>.
+ /// @param length Length of bytes to copy from <source> to <dest>
+ /// @return mem Memory contents after calling memCopy.
+ function testMemcpy(
+ bytes mem,
+ uint256 dest,
+ uint256 source,
+ uint256 length
+ )
+ public // not external, we need input in memory
+ pure
+ returns (bytes)
+ {
+ // Sanity check. Overflows are not checked.
+ require(source + length <= mem.length);
+ require(dest + length <= mem.length);
+
+ // Get pointer to memory contents
+ uint256 offset = mem.contentAddress();
+
+ // Execute memCopy adjusted for memory array location
+ LibBytes.memCopy(offset + dest, offset + source, length);
+
+ // Return modified memory contents
+ return mem;
+ }
}
diff --git a/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol b/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol
deleted file mode 100644
index b7e2e06b8..000000000
--- a/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-
- Copyright 2018 ZeroEx Intl.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "../../utils/LibMem/LibMem.sol";
-
-contract TestLibMem is
- LibMem
-{
-
- /// @dev Copies a block of memory from one location to another.
- /// @param mem Memory contents we want to apply memCopy to
- /// @param dest Destination offset into <mem>.
- /// @param source Source offset into <mem>.
- /// @param length Length of bytes to copy from <source> to <dest>
- /// @return mem Memory contents after calling memCopy.
- function testMemcpy(
- bytes mem,
- uint256 dest,
- uint256 source,
- uint256 length
- )
- public // not external, we need input in memory
- pure
- returns (bytes)
- {
- // Sanity check. Overflows are not checked.
- require(source + length <= mem.length);
- require(dest + length <= mem.length);
-
- // Get pointer to memory contents
- uint256 offset = getMemAddress(mem) + 32;
-
- // Execute memCopy adjusted for memory array location
- memCopy(offset + dest, offset + source, length);
-
- // Return modified memory contents
- return mem;
- }
-}
diff --git a/packages/contracts/src/contracts/current/test/TestWallet/TestWallet.sol b/packages/contracts/src/contracts/current/test/TestWallet/TestWallet.sol
index aca74b409..17dee9e9c 100644
--- a/packages/contracts/src/contracts/current/test/TestWallet/TestWallet.sol
+++ b/packages/contracts/src/contracts/current/test/TestWallet/TestWallet.sol
@@ -22,9 +22,9 @@ import "../../protocol/Exchange/interfaces/IWallet.sol";
import "../../utils/LibBytes/LibBytes.sol";
contract TestWallet is
- IWallet,
- LibBytes
+ IWallet
{
+ using LibBytes for bytes;
string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED";
@@ -56,8 +56,8 @@ contract TestWallet is
);
uint8 v = uint8(eip712Signature[0]);
- bytes32 r = readBytes32(eip712Signature, 1);
- bytes32 s = readBytes32(eip712Signature, 33);
+ bytes32 r = eip712Signature.readBytes32(1);
+ bytes32 s = eip712Signature.readBytes32(33);
address recoveredAddress = ecrecover(hash, v, r, s);
isValid = WALLET_OWNER == recoveredAddress;
return isValid;
diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
index 10d7ce41a..e4cbf318b 100644
--- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
+++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
@@ -18,11 +18,8 @@
pragma solidity ^0.4.24;
-import "../LibMem/LibMem.sol";
-
-contract LibBytes is
- LibMem
-{
+library LibBytes {
+ using LibBytes for bytes;
// Revert reasons
string constant GREATER_THAN_ZERO_LENGTH_REQUIRED = "GREATER_THAN_ZERO_LENGTH_REQUIRED";
@@ -31,6 +28,187 @@ contract LibBytes is
string constant GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED";
string constant GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED";
string constant GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED";
+ string constant FROM_LESS_THAN_TO_REQUIRED = "FROM_LESS_THAN_TO_REQUIRED";
+ string constant TO_LESS_THAN_LENGTH_REQUIRED = "TO_LESS_THAN_LENGTH_REQUIRED";
+
+ /// @dev Gets the memory address for a byte array.
+ /// @param input Byte array to lookup.
+ /// @return memoryAddress Memory address of byte array. This
+ /// points to the header of the byte array which contains
+ /// the length.
+ function rawAddress(bytes memory input)
+ internal
+ pure
+ returns (uint256 memoryAddress)
+ {
+ assembly {
+ memoryAddress := input
+ }
+ return memoryAddress;
+ }
+
+ /// @dev Gets the memory address for the contents of a byte array.
+ /// @param input Byte array to lookup.
+ /// @return memoryAddress Memory address of the contents of the byte array.
+ function contentAddress(bytes memory input)
+ internal
+ pure
+ returns (uint256 memoryAddress)
+ {
+ assembly {
+ memoryAddress := add(input, 32)
+ }
+ return memoryAddress;
+ }
+
+ /// @dev Copies `length` bytes from memory location `source` to `dest`.
+ /// @param dest memory address to copy bytes to.
+ /// @param source memory address to copy bytes from.
+ /// @param length number of bytes to copy.
+ function memCopy(
+ uint256 dest,
+ uint256 source,
+ uint256 length
+ )
+ internal
+ pure
+ {
+ if (length < 32) {
+ // Handle a partial word by reading destination and masking
+ // off the bits we are interested in.
+ // This correctly handles overlap, zero lengths and source == dest
+ assembly {
+ let mask := sub(exp(256, sub(32, length)), 1)
+ let s := and(mload(source), not(mask))
+ let d := and(mload(dest), mask)
+ mstore(dest, or(s, d))
+ }
+ } else {
+ // Skip the O(length) loop when source == dest.
+ if (source == dest) {
+ return;
+ }
+
+ // For large copies we copy whole words at a time. The final
+ // word is aligned to the end of the range (instead of after the
+ // previous) to handle partial words. So a copy will look like this:
+ //
+ // ####
+ // ####
+ // ####
+ // ####
+ //
+ // We handle overlap in the source and destination range by
+ // changing the copying direction. This prevents us from
+ // overwriting parts of source that we still need to copy.
+ //
+ // This correctly handles source == dest
+ //
+ if (source > dest) {
+ assembly {
+ // We subtract 32 from `sEnd` and `dEnd` because it
+ // is easier to compare with in the loop, and these
+ // are also the addresses we need for copying the
+ // last bytes.
+ length := sub(length, 32)
+ let sEnd := add(source, length)
+ let dEnd := add(dest, length)
+
+ // Remember the last 32 bytes of source
+ // This needs to be done here and not after the loop
+ // because we may have overwritten the last bytes in
+ // source already due to overlap.
+ let last := mload(sEnd)
+
+ // Copy whole words front to back
+ // Note: the first check is always true,
+ // this could have been a do-while loop.
+ for {} lt(source, sEnd) {} {
+ mstore(dest, mload(source))
+ source := add(source, 32)
+ dest := add(dest, 32)
+ }
+
+ // Write the last 32 bytes
+ mstore(dEnd, last)
+ }
+ } else {
+ assembly {
+ // We subtract 32 from `sEnd` and `dEnd` because those
+ // are the starting points when copying a word at the end.
+ length := sub(length, 32)
+ let sEnd := add(source, length)
+ let dEnd := add(dest, length)
+
+ // Remember the first 32 bytes of source
+ // This needs to be done here and not after the loop
+ // because we may have overwritten the first bytes in
+ // source already due to overlap.
+ let first := mload(source)
+
+ // Copy whole words back to front
+ // We use a signed comparisson here to allow dEnd to become
+ // negative (happens when source and dest < 32). Valid
+ // addresses in local memory will never be larger than
+ // 2**255, so they can be safely re-interpreted as signed.
+ // Note: the first check is always true,
+ // this could have been a do-while loop.
+ for {} slt(dest, dEnd) {} {
+ mstore(dEnd, mload(sEnd))
+ sEnd := sub(sEnd, 32)
+ dEnd := sub(dEnd, 32)
+ }
+
+ // Write the first 32 bytes
+ mstore(dest, first)
+ }
+ }
+ }
+ }
+
+ /// @dev Returns a slices from a byte array.
+ /// @param b The byte array to take a slice from.
+ /// @param from The starting index for the slice (inclusive).
+ /// @param to The final index for the slice (exclusive).
+ /// @return result The slice containing bytes at indices [from, to)
+ function slice(bytes memory b, uint256 from, uint256 to)
+ internal
+ pure
+ returns (bytes memory result)
+ {
+ require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
+ require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
+
+ // Create a new bytes structure and copy contents
+ result = new bytes(to - from);
+ memCopy(
+ result.contentAddress(),
+ b.contentAddress() + from,
+ result.length);
+ return result;
+ }
+
+ /// @dev Returns a slice from a byte array without preserving the input.
+ /// @param b The byte array to take a slice from. Will be destroyed in the process.
+ /// @param from The starting index for the slice (inclusive).
+ /// @param to The final index for the slice (exclusive).
+ /// @return result The slice containing bytes at indices [from, to)
+ /// @dev When `from == 0`, the original array will match the slice. In other cases its state will be corrupted.
+ function sliceDestructive(bytes memory b, uint256 from, uint256 to)
+ internal
+ pure
+ returns (bytes memory result)
+ {
+ require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
+ require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
+
+ // Create a new bytes structure around [from, to) in-place.
+ assembly {
+ result := add(b, from)
+ mstore(result, sub(to, from))
+ }
+ return result;
+ }
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
@@ -80,6 +258,24 @@ contract LibBytes is
return result;
}
+ /// @dev Tests equality of two byte arrays.
+ /// @param lhs First byte array to compare.
+ /// @param rhs Second byte array to compare.
+ /// @return True if arrays are the same. False otherwise.
+ function equals(
+ bytes memory lhs,
+ bytes memory rhs
+ )
+ internal
+ pure
+ returns (bool equal)
+ {
+ // Keccak gas cost is 30 + numWords * 6. This is a cheap way to compare.
+ // We early exit on unequal lengths, but keccak would also correctly
+ // handle this.
+ return lhs.length == rhs.length && keccak256(lhs) == keccak256(rhs);
+ }
+
/// @dev Reads an address from a position in a byte array.
/// @param b Byte array containing an address.
/// @param index Index in byte array of address.
@@ -145,6 +341,10 @@ contract LibBytes is
// 2. Load 32-byte word from memory
// 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address
let neighbors := and(mload(add(b, index)), 0xffffffffffffffffffffffff0000000000000000000000000000000000000000)
+
+ // Make sure input address is clean.
+ // (Solidity does not guarantee this)
+ input := and(input, 0xffffffffffffffffffffffffffffffffffffffff)
// Store the neighbors and address into memory
mstore(add(b, index), xor(input, neighbors))
@@ -234,20 +434,26 @@ contract LibBytes is
writeBytes32(b, index, bytes32(input));
}
- /// @dev Reads the first 4 bytes from a byte array of arbitrary length.
- /// @param b Byte array to read first 4 bytes from.
- /// @return First 4 bytes of data.
- function readFirst4(bytes memory b)
+ /// @dev Reads an unpadded bytes4 value from a position in a byte array.
+ /// @param b Byte array containing a bytes4 value.
+ /// @param index Index in byte array of bytes4 value.
+ /// @return bytes4 value from byte array.
+ function readBytes4(
+ bytes memory b,
+ uint256 index)
internal
pure
returns (bytes4 result)
{
require(
- b.length >= 4,
+ b.length >= index + 4,
GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED
);
assembly {
result := mload(add(b, 32))
+ // Solidity does not require us to clean the trailing bytes.
+ // We do it anyway
+ result := and(result, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
}
return result;
}
@@ -256,7 +462,7 @@ contract LibBytes is
/// @param b Byte array containing nested bytes.
/// @param index Index of nested bytes.
/// @return result Nested bytes.
- function readBytes(
+ function readBytesWithLength(
bytes memory b,
uint256 index
)
@@ -278,8 +484,8 @@ contract LibBytes is
// Allocate memory and copy value to result
result = new bytes(nestedBytesLength);
memCopy(
- getMemAddress(result) + 32, // +32 skips array length
- getMemAddress(b) + index + 32,
+ result.contentAddress(),
+ b.contentAddress() + index,
nestedBytesLength
);
@@ -290,7 +496,7 @@ contract LibBytes is
/// @param b Byte array to insert <input> into.
/// @param index Index in byte array of <input>.
/// @param input bytes to insert.
- function writeBytes(
+ function writeBytesWithLength(
bytes memory b,
uint256 index,
bytes memory input
@@ -307,47 +513,12 @@ contract LibBytes is
// Copy <input> into <b>
memCopy(
- getMemAddress(b) + 32 + index, // +32 to skip length of <b>
- getMemAddress(input), // includes length of <input>
- input.length + 32 // +32 bytes to store <input> length
+ b.contentAddress() + index,
+ input.rawAddress(), // includes length of <input>
+ input.length + 32 // +32 bytes to store <input> length
);
}
- /// @dev Tests equality of two byte arrays.
- /// @param lhs First byte array to compare.
- /// @param rhs Second byte array to compare.
- /// @return True if arrays are the same. False otherwise.
- function areBytesEqual(
- bytes memory lhs,
- bytes memory rhs
- )
- internal
- pure
- returns (bool equal)
- {
- assembly {
- // Get the number of words occupied by <lhs>
- let lenFullWords := div(add(mload(lhs), 0x1F), 0x20)
-
- // Add 1 to the number of words, to account for the length field
- lenFullWords := add(lenFullWords, 0x1)
-
- // Test equality word-by-word.
- // Terminates early if there is a mismatch.
- for {let i := 0} lt(i, lenFullWords) {i := add(i, 1)} {
- let lhsWord := mload(add(lhs, mul(i, 0x20)))
- let rhsWord := mload(add(rhs, mul(i, 0x20)))
- equal := eq(lhsWord, rhsWord)
- if eq(equal, 0) {
- // Break
- i := lenFullWords
- }
- }
- }
-
- return equal;
- }
-
/// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length.
/// @param dest Byte array that will be overwritten with source bytes.
/// @param source Byte array to copy onto dest bytes.
@@ -365,8 +536,8 @@ contract LibBytes is
GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED
);
memCopy(
- getMemAddress(dest) + 32, // +32 to skip length of <dest>
- getMemAddress(source) + 32, // +32 to skip length of <source>
+ dest.contentAddress(),
+ source.contentAddress(),
sourceLen
);
}
diff --git a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
deleted file mode 100644
index 97fb5fb0f..000000000
--- a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
-
- Copyright 2018 ZeroEx Intl.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-contract LibMem
-{
-
- /// @dev Gets the memory address for a byte array.
- /// @param input Byte array to lookup.
- /// @return memoryAddress Memory address of byte array.
- function getMemAddress(bytes memory input)
- internal
- pure
- returns (uint256 memoryAddress)
- {
- assembly {
- memoryAddress := input
- }
- return memoryAddress;
- }
-
- /// @dev Copies `length` bytes from memory location `source` to `dest`.
- /// @param dest memory address to copy bytes to.
- /// @param source memory address to copy bytes from.
- /// @param length number of bytes to copy.
- function memCopy(
- uint256 dest,
- uint256 source,
- uint256 length
- )
- internal
- pure
- {
- if (length < 32) {
- // Handle a partial word by reading destination and masking
- // off the bits we are interested in.
- // This correctly handles overlap, zero lengths and source == dest
- assembly {
- let mask := sub(exp(256, sub(32, length)), 1)
- let s := and(mload(source), not(mask))
- let d := and(mload(dest), mask)
- mstore(dest, or(s, d))
- }
- } else {
- // Skip the O(length) loop when source == dest.
- if (source == dest) {
- return;
- }
-
- // For large copies we copy whole words at a time. The final
- // word is aligned to the end of the range (instead of after the
- // previous) to handle partial words. So a copy will look like this:
- //
- // ####
- // ####
- // ####
- // ####
- //
- // We handle overlap in the source and destination range by
- // changing the copying direction. This prevents us from
- // overwriting parts of source that we still need to copy.
- //
- // This correctly handles source == dest
- //
- if (source > dest) {
- assembly {
- // We subtract 32 from `sEnd` and `dEnd` because it
- // is easier to compare with in the loop, and these
- // are also the addresses we need for copying the
- // last bytes.
- length := sub(length, 32)
- let sEnd := add(source, length)
- let dEnd := add(dest, length)
-
- // Remember the last 32 bytes of source
- // This needs to be done here and not after the loop
- // because we may have overwritten the last bytes in
- // source already due to overlap.
- let last := mload(sEnd)
-
- // Copy whole words front to back
- // Note: the first check is always true,
- // this could have been a do-while loop.
- for {} lt(source, sEnd) {} {
- mstore(dest, mload(source))
- source := add(source, 32)
- dest := add(dest, 32)
- }
-
- // Write the last 32 bytes
- mstore(dEnd, last)
- }
- } else {
- assembly {
- // We subtract 32 from `sEnd` and `dEnd` because those
- // are the starting points when copying a word at the end.
- length := sub(length, 32)
- let sEnd := add(source, length)
- let dEnd := add(dest, length)
-
- // Remember the first 32 bytes of source
- // This needs to be done here and not after the loop
- // because we may have overwritten the first bytes in
- // source already due to overlap.
- let first := mload(source)
-
- // Copy whole words back to front
- // We use a signed comparisson here to allow dEnd to become
- // negative (happens when source and dest < 32). Valid
- // addresses in local memory will never be larger than
- // 2**255, so they can be safely re-interpreted as signed.
- // Note: the first check is always true,
- // this could have been a do-while loop.
- for {} slt(dest, dEnd) {} {
- mstore(dEnd, mload(sEnd))
- sEnd := sub(sEnd, 32)
- dEnd := sub(dEnd, 32)
- }
-
- // Write the first 32 bytes
- mstore(dest, first)
- }
- }
- }
- }
-}
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
index 048fdb46f..99b93d0d2 100644
--- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
+++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
@@ -13,6 +13,9 @@ import "./IOwnable.sol";
contract Ownable is IOwnable {
address public owner;
+ // Revert reasons
+ string constant ONLY_CONTRACT_OWNER = "ONLY_CONTRACT_OWNER";
+
constructor ()
public
{
@@ -22,7 +25,7 @@ contract Ownable is IOwnable {
modifier onlyOwner() {
require(
msg.sender == owner,
- 'ONLY_CONTRACT_OWNER'
+ ONLY_CONTRACT_OWNER
);
_;
}
diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts
index 4375d87c6..a46bab9ae 100644
--- a/packages/contracts/src/utils/artifacts.ts
+++ b/packages/contracts/src/utils/artifacts.ts
@@ -13,8 +13,8 @@ import * as MultiSigWallet from '../artifacts/MultiSigWallet.json';
import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json';
import * as TestAssetDataDecoders from '../artifacts/TestAssetDataDecoders.json';
import * as TestAssetProxyDispatcher from '../artifacts/TestAssetProxyDispatcher.json';
+import * as TestAssetProxyOwner from '../artifacts/TestAssetProxyOwner.json';
import * as TestLibBytes from '../artifacts/TestLibBytes.json';
-import * as TestLibMem from '../artifacts/TestLibMem.json';
import * as TestLibs from '../artifacts/TestLibs.json';
import * as TestSignatureValidator from '../artifacts/TestSignatureValidator.json';
import * as TestValidator from '../artifacts/TestValidator.json';
@@ -37,10 +37,10 @@ export const artifacts = {
MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact,
MultiSigWallet: (MultiSigWallet as any) as ContractArtifact,
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
+ TestAssetProxyOwner: (TestAssetProxyOwner as any) as ContractArtifact,
TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact,
TestAssetDataDecoders: (TestAssetDataDecoders as any) as ContractArtifact,
TestLibBytes: (TestLibBytes as any) as ContractArtifact,
- TestLibMem: (TestLibMem as any) as ContractArtifact,
TestLibs: (TestLibs as any) as ContractArtifact,
TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact,
TestValidator: (TestValidator as any) as ContractArtifact,
diff --git a/packages/contracts/src/utils/multi_sig_wrapper.ts b/packages/contracts/src/utils/multi_sig_wrapper.ts
index f0098bd5e..b0d4fa8ab 100644
--- a/packages/contracts/src/utils/multi_sig_wrapper.ts
+++ b/packages/contracts/src/utils/multi_sig_wrapper.ts
@@ -40,13 +40,15 @@ export class MultiSigWrapper {
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
- public async executeRemoveAuthorizedAddressAsync(
+ public async executeRemoveAuthorizedAddressAtIndexAsync(
txId: BigNumber,
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
// tslint:disable-next-line:no-unnecessary-type-assertion
const txHash = await (this
- ._multiSig as AssetProxyOwnerContract).executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from });
+ ._multiSig as AssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
+ from,
+ });
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index 03eb5c9b2..835e7dda8 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -92,7 +92,6 @@ export enum ContractName {
Arbitrage = 'Arbitrage',
TestAssetDataDecoders = 'TestAssetDataDecoders',
TestAssetProxyDispatcher = 'TestAssetProxyDispatcher',
- TestLibMem = 'TestLibMem',
TestLibs = 'TestLibs',
TestSignatureValidator = 'TestSignatureValidator',
ERC20Proxy = 'ERC20Proxy',
diff --git a/packages/contracts/test/asset_proxy/authorizable.ts b/packages/contracts/test/asset_proxy/authorizable.ts
index a317f217a..24ec73a64 100644
--- a/packages/contracts/test/asset_proxy/authorizable.ts
+++ b/packages/contracts/test/asset_proxy/authorizable.ts
@@ -1,4 +1,5 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { MixinAuthorizableContract } from '../../src/generated_contract_wrappers/mixin_authorizable';
@@ -107,6 +108,74 @@ describe('Authorizable', () => {
});
});
+ describe('removeAuthorizedAddressAtIndex', () => {
+ it('should throw if not called by owner', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(0);
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: notOwner,
+ }),
+ );
+ });
+ it('should throw if index is >= authorities.length', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(1);
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ );
+ });
+ it('should throw if owner attempts to remove an address that is not authorized', async () => {
+ const index = new BigNumber(0);
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ );
+ });
+ it('should throw if address at index does not match target', async () => {
+ const address1 = address;
+ const address2 = notOwner;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address1, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address2, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const address1Index = new BigNumber(0);
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, {
+ from: owner,
+ }),
+ );
+ });
+ it('should allow owner to remove an authorized address', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const index = new BigNumber(0);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.false();
+ });
+ });
+
describe('getAuthorizedAddresses', () => {
it('should return all authorized addresses', async () => {
const initial = await authorizable.getAuthorizedAddresses.callAsync();
diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts
index 875d55daa..98d18aa38 100644
--- a/packages/contracts/test/asset_proxy/decoder.ts
+++ b/packages/contracts/test/asset_proxy/decoder.ts
@@ -27,7 +27,6 @@ describe('TestAssetDataDecoders', () => {
// Setup accounts & addresses
const accounts = await web3Wrapper.getAvailableAddressesAsync();
testAddress = accounts[0];
- // Deploy TestLibMem
testAssetProxyDecoder = await TestAssetDataDecodersContract.deployFrom0xArtifactAsync(
artifacts.TestAssetDataDecoders,
provider,
diff --git a/packages/contracts/test/asset_proxy_owner.ts b/packages/contracts/test/asset_proxy_owner.ts
index aa4999e95..188cba5a3 100644
--- a/packages/contracts/test/asset_proxy_owner.ts
+++ b/packages/contracts/test/asset_proxy_owner.ts
@@ -11,6 +11,7 @@ import {
SubmissionContractEventArgs,
} from '../src/generated_contract_wrappers/asset_proxy_owner';
import { MixinAuthorizableContract } from '../src/generated_contract_wrappers/mixin_authorizable';
+import { TestAssetProxyOwnerContract } from '../src/generated_contract_wrappers/test_asset_proxy_owner';
import { artifacts } from '../src/utils/artifacts';
import {
expectRevertOrAlwaysFailingTransactionAsync,
@@ -34,7 +35,7 @@ describe('AssetProxyOwner', () => {
let erc20Proxy: MixinAuthorizableContract;
let erc721Proxy: MixinAuthorizableContract;
- let multiSig: AssetProxyOwnerContract;
+ let testAssetProxyOwner: TestAssetProxyOwnerContract;
let multiSigWrapper: MultiSigWrapper;
before(async () => {
@@ -58,8 +59,8 @@ describe('AssetProxyOwner', () => {
txDefaults,
);
const defaultAssetProxyContractAddresses: string[] = [];
- multiSig = await AssetProxyOwnerContract.deployFrom0xArtifactAsync(
- artifacts.AssetProxyOwner,
+ testAssetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync(
+ artifacts.TestAssetProxyOwner,
provider,
txDefaults,
owners,
@@ -67,13 +68,17 @@ describe('AssetProxyOwner', () => {
REQUIRED_APPROVALS,
SECONDS_TIME_LOCKED,
);
- multiSigWrapper = new MultiSigWrapper(multiSig, provider);
+ multiSigWrapper = new MultiSigWrapper(testAssetProxyOwner, provider);
await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20Proxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: initialOwner }),
+ await erc20Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, {
+ from: initialOwner,
+ }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
- await erc721Proxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: initialOwner }),
+ await erc721Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, {
+ from: initialOwner,
+ }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
});
@@ -117,24 +122,28 @@ describe('AssetProxyOwner', () => {
});
});
- describe('isFunctionRemoveAuthorizedAddress', () => {
- it('should throw if data is not for removeAuthorizedAddress', async () => {
+ describe('isFunctionRemoveAuthorizedAddressAtIndex', () => {
+ it('should return false if data is not for removeAuthorizedAddressAtIndex', async () => {
const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(
owners[0],
);
- return expectRevertOrContractCallFailedAsync(
- multiSig.isFunctionRemoveAuthorizedAddress.callAsync(notRemoveAuthorizedAddressData),
+
+ const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync(
+ notRemoveAuthorizedAddressData,
);
+ expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.false();
});
- it('should return true if data is for removeAuthorizedAddress', async () => {
- const removeAuthorizedAddressData = erc20Proxy.removeAuthorizedAddress.getABIEncodedTransactionData(
+ it('should return true if data is for removeAuthorizedAddressAtIndex', async () => {
+ const index = new BigNumber(0);
+ const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
owners[0],
+ index,
);
- const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.callAsync(
- removeAuthorizedAddressData,
+ const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync(
+ removeAuthorizedAddressAtIndexData,
);
- expect(isFunctionRemoveAuthorizedAddress).to.be.true();
+ expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.true();
});
});
@@ -142,19 +151,21 @@ describe('AssetProxyOwner', () => {
it('should throw if not called by multisig', async () => {
const isRegistered = true;
return expectRevertOrAlwaysFailingTransactionAsync(
- multiSig.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, { from: owners[0] }),
+ testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, {
+ from: owners[0],
+ }),
);
});
it('should register an address if called by multisig after timelock', async () => {
const addressToRegister = erc20Proxy.address;
const isRegistered = true;
- const registerAssetProxyData = multiSig.registerAssetProxy.getABIEncodedTransactionData(
+ const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData(
addressToRegister,
isRegistered,
);
const submitTxRes = await multiSigWrapper.submitTransactionAsync(
- multiSig.address,
+ testAssetProxyOwner.address,
registerAssetProxyData,
owners[0],
);
@@ -170,19 +181,21 @@ describe('AssetProxyOwner', () => {
expect(registerLog.args.assetProxyContract).to.equal(addressToRegister);
expect(registerLog.args.isRegistered).to.equal(isRegistered);
- const isAssetProxyRegistered = await multiSig.isAssetProxyRegistered.callAsync(addressToRegister);
+ const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync(
+ addressToRegister,
+ );
expect(isAssetProxyRegistered).to.equal(isRegistered);
});
it('should fail if registering a null address', async () => {
const addressToRegister = constants.NULL_ADDRESS;
const isRegistered = true;
- const registerAssetProxyData = multiSig.registerAssetProxy.getABIEncodedTransactionData(
+ const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData(
addressToRegister,
isRegistered,
);
const submitTxRes = await multiSigWrapper.submitTransactionAsync(
- multiSig.address,
+ testAssetProxyOwner.address,
registerAssetProxyData,
owners[0],
);
@@ -196,22 +209,26 @@ describe('AssetProxyOwner', () => {
const failureLog = executeTxRes.logs[0] as LogWithDecodedArgs<ExecutionFailureContractEventArgs>;
expect(failureLog.args.transactionId).to.be.bignumber.equal(txId);
- const isAssetProxyRegistered = await multiSig.isAssetProxyRegistered.callAsync(addressToRegister);
+ const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync(
+ addressToRegister,
+ );
expect(isAssetProxyRegistered).to.equal(false);
});
});
- describe('executeRemoveAuthorizedAddress', () => {
+ describe('Calling removeAuthorizedAddressAtIndex', () => {
+ const erc20Index = new BigNumber(0);
+ const erc721Index = new BigNumber(1);
before('authorize both proxies and register erc20 proxy', async () => {
// Only register ERC20 proxy
const addressToRegister = erc20Proxy.address;
const isRegistered = true;
- const registerAssetProxyData = multiSig.registerAssetProxy.getABIEncodedTransactionData(
+ const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData(
addressToRegister,
isRegistered,
);
const registerAssetProxySubmitRes = await multiSigWrapper.submitTransactionAsync(
- multiSig.address,
+ testAssetProxyOwner.address,
registerAssetProxyData,
owners[0],
);
@@ -248,113 +265,180 @@ describe('AssetProxyOwner', () => {
await multiSigWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0]);
});
- it('should throw without the required confirmations', async () => {
- const removeAuthorizedAddressData = erc20Proxy.removeAuthorizedAddress.getABIEncodedTransactionData(
- authorized,
- );
- const res = await multiSigWrapper.submitTransactionAsync(
- erc20Proxy.address,
- removeAuthorizedAddressData,
- owners[0],
- );
- const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
- const txId = log.args.transactionId;
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }),
- );
- });
-
- it('should throw if tx destination is not registered', async () => {
- const removeAuthorizedAddressData = erc721Proxy.removeAuthorizedAddress.getABIEncodedTransactionData(
- authorized,
- );
- const res = await multiSigWrapper.submitTransactionAsync(
- erc721Proxy.address,
- removeAuthorizedAddressData,
- owners[0],
- );
- const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
- const txId = log.args.transactionId;
-
- await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }),
- );
- });
-
- it('should throw if tx data is not for removeAuthorizedAddress', async () => {
- const newAuthorized = owners[1];
- const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(
- newAuthorized,
- );
- const res = await multiSigWrapper.submitTransactionAsync(
- erc20Proxy.address,
- addAuthorizedAddressData,
- owners[0],
- );
- const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
- const txId = log.args.transactionId;
-
- await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }),
- );
- });
-
- it('should execute removeAuthorizedAddress for registered address if fully confirmed', async () => {
- const removeAuthorizedAddressData = erc20Proxy.removeAuthorizedAddress.getABIEncodedTransactionData(
- authorized,
- );
- const submitRes = await multiSigWrapper.submitTransactionAsync(
- erc20Proxy.address,
- removeAuthorizedAddressData,
- owners[0],
- );
- const submitLog = submitRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
- const txId = submitLog.args.transactionId;
-
- await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
-
- const execRes = await multiSigWrapper.executeRemoveAuthorizedAddressAsync(txId, owners[0]);
- const execLog = execRes.logs[0] as LogWithDecodedArgs<ExecutionContractEventArgs>;
- expect(execLog.args.transactionId).to.be.bignumber.equal(txId);
-
- const tx = await multiSig.transactions.callAsync(txId);
- const isExecuted = tx[3];
- expect(isExecuted).to.equal(true);
-
- const isAuthorized = await erc20Proxy.authorized.callAsync(authorized);
- expect(isAuthorized).to.equal(false);
+ describe('validRemoveAuthorizedAddressAtIndexTx', () => {
+ it('should revert if data is not for removeAuthorizedAddressAtIndex and proxy is registered', async () => {
+ const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(
+ authorized,
+ );
+ const submitTxRes = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ notRemoveAuthorizedAddressData,
+ owners[0],
+ );
+ const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+ return expectRevertOrContractCallFailedAsync(
+ testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId),
+ );
+ });
+
+ it('should return true if data is for removeAuthorizedAddressAtIndex and proxy is registered', async () => {
+ const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc20Index,
+ );
+ const submitTxRes = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+ const isValidRemoveAuthorizedAddressAtIndexTx = await testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(
+ txId,
+ );
+ expect(isValidRemoveAuthorizedAddressAtIndexTx).to.be.true();
+ });
+
+ it('should revert if data is for removeAuthorizedAddressAtIndex and proxy is not registered', async () => {
+ const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc721Index,
+ );
+ const submitTxRes = await multiSigWrapper.submitTransactionAsync(
+ erc721Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+ return expectRevertOrContractCallFailedAsync(
+ testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId),
+ );
+ });
});
- it('should throw if already executed', async () => {
- const removeAuthorizedAddressData = erc20Proxy.removeAuthorizedAddress.getABIEncodedTransactionData(
- authorized,
- );
- const submitRes = await multiSigWrapper.submitTransactionAsync(
- erc20Proxy.address,
- removeAuthorizedAddressData,
- owners[0],
- );
- const submitLog = submitRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
- const txId = submitLog.args.transactionId;
-
- await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
-
- const execRes = await multiSigWrapper.executeRemoveAuthorizedAddressAsync(txId, owners[0]);
- const execLog = execRes.logs[0] as LogWithDecodedArgs<ExecutionContractEventArgs>;
- expect(execLog.args.transactionId).to.be.bignumber.equal(txId);
-
- const tx = await multiSig.transactions.callAsync(txId);
- const isExecuted = tx[3];
- expect(isExecuted).to.equal(true);
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }),
- );
+ describe('executeRemoveAuthorizedAddressAtIndex', () => {
+ it('should throw without the required confirmations', async () => {
+ const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc20Index,
+ );
+ const res = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
+ from: owners[1],
+ }),
+ );
+ });
+
+ it('should throw if tx destination is not registered', async () => {
+ const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc721Index,
+ );
+ const res = await multiSigWrapper.submitTransactionAsync(
+ erc721Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+
+ await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
+
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
+ from: owners[1],
+ }),
+ );
+ });
+
+ it('should throw if tx data is not for removeAuthorizedAddressAtIndex', async () => {
+ const newAuthorized = owners[1];
+ const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(
+ newAuthorized,
+ );
+ const res = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ addAuthorizedAddressData,
+ owners[0],
+ );
+ const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = log.args.transactionId;
+
+ await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
+
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
+ from: owners[1],
+ }),
+ );
+ });
+
+ it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed', async () => {
+ const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc20Index,
+ );
+ const submitRes = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const submitLog = submitRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = submitLog.args.transactionId;
+
+ await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
+
+ const execRes = await multiSigWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, owners[0]);
+ const execLog = execRes.logs[0] as LogWithDecodedArgs<ExecutionContractEventArgs>;
+ expect(execLog.args.transactionId).to.be.bignumber.equal(txId);
+
+ const tx = await testAssetProxyOwner.transactions.callAsync(txId);
+ const isExecuted = tx[3];
+ expect(isExecuted).to.equal(true);
+
+ const isAuthorized = await erc20Proxy.authorized.callAsync(authorized);
+ expect(isAuthorized).to.equal(false);
+ });
+
+ it('should throw if already executed', async () => {
+ const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
+ authorized,
+ erc20Index,
+ );
+ const submitRes = await multiSigWrapper.submitTransactionAsync(
+ erc20Proxy.address,
+ removeAuthorizedAddressAtIndexData,
+ owners[0],
+ );
+ const submitLog = submitRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
+ const txId = submitLog.args.transactionId;
+
+ await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
+
+ const execRes = await multiSigWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, owners[0]);
+ const execLog = execRes.logs[0] as LogWithDecodedArgs<ExecutionContractEventArgs>;
+ expect(execLog.args.transactionId).to.be.bignumber.equal(txId);
+
+ const tx = await testAssetProxyOwner.transactions.callAsync(txId);
+ const isExecuted = tx[3];
+ expect(isExecuted).to.equal(true);
+
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
+ from: owners[1],
+ }),
+ );
+ });
});
});
});
diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts
index 7db0f60b5..e4b3abb7f 100644
--- a/packages/contracts/test/libraries/lib_bytes.ts
+++ b/packages/contracts/test/libraries/lib_bytes.ts
@@ -18,6 +18,12 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+// BUG: Ideally we would use Buffer.from(memory).toString('hex')
+// https://github.com/Microsoft/TypeScript/issues/23155
+const toHex = (buf: Uint8Array): string => buf.reduce((a, v) => a + ('00' + v.toString(16)).slice(-2), '0x');
+
+const fromHex = (str: string): Uint8Array => Uint8Array.from(Buffer.from(str.slice(2), 'hex'));
+
describe('LibBytes', () => {
let libBytes: TestLibBytesContract;
const byteArrayShorterThan32Bytes = '0x012345';
@@ -124,48 +130,55 @@ describe('LibBytes', () => {
});
});
- describe('areBytesEqual', () => {
+ describe('equals', () => {
it('should return true if byte arrays are equal (both arrays < 32 bytes)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayShorterThan32Bytes,
byteArrayShorterThan32Bytes,
);
- return expect(areBytesEqual).to.be.true();
+ return expect(isEqual).to.be.true();
});
it('should return true if byte arrays are equal (both arrays > 32 bytes)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayLongerThan32Bytes,
byteArrayLongerThan32Bytes,
);
- return expect(areBytesEqual).to.be.true();
+ return expect(isEqual).to.be.true();
});
it('should return false if byte arrays are not equal (first array < 32 bytes, second array > 32 bytes)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayShorterThan32Bytes,
byteArrayLongerThan32Bytes,
);
- return expect(areBytesEqual).to.be.false();
+ return expect(isEqual).to.be.false();
});
it('should return false if byte arrays are not equal (first array > 32 bytes, second array < 32 bytes)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayLongerThan32Bytes,
byteArrayShorterThan32Bytes,
);
- return expect(areBytesEqual).to.be.false();
+ return expect(isEqual).to.be.false();
});
it('should return false if byte arrays are not equal (same length, but a byte in first word differs)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayLongerThan32BytesFirstBytesSwapped,
byteArrayLongerThan32Bytes,
);
- return expect(areBytesEqual).to.be.false();
+ return expect(isEqual).to.be.false();
});
it('should return false if byte arrays are not equal (same length, but a byte in last word differs)', async () => {
- const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ const isEqual = await libBytes.publicEquals.callAsync(
byteArrayLongerThan32BytesLastBytesSwapped,
byteArrayLongerThan32Bytes,
);
- return expect(areBytesEqual).to.be.false();
+ return expect(isEqual).to.be.false();
+ });
+
+ describe('should ignore trailing data', () => {
+ it('should return true when both < 32 bytes', async () => {
+ const isEqual = await libBytes.publicEqualsPop1.callAsync('0x0102', '0x0103');
+ return expect(isEqual).to.be.true();
+ });
});
});
@@ -444,26 +457,26 @@ describe('LibBytes', () => {
});
});
- describe('readFirst4', () => {
+ describe('readBytes4', () => {
// AssertionError: expected promise to be rejected with an error including 'revert' but it was fulfilled with '0x08c379a0'
it('should revert if byte array has a length < 4', async () => {
const byteArrayLessThan4Bytes = '0x010101';
return expectRevertOrOtherErrorAsync(
- libBytes.publicReadFirst4.callAsync(byteArrayLessThan4Bytes),
+ libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, new BigNumber(0)),
RevertReasons.LibBytesGreaterOrEqualTo4LengthRequired,
);
});
it('should return the first 4 bytes of a byte array of arbitrary length', async () => {
- const first4Bytes = await libBytes.publicReadFirst4.callAsync(byteArrayLongerThan32Bytes);
+ const first4Bytes = await libBytes.publicReadBytes4.callAsync(byteArrayLongerThan32Bytes, new BigNumber(0));
const expectedFirst4Bytes = byteArrayLongerThan32Bytes.slice(0, 10);
expect(first4Bytes).to.equal(expectedFirst4Bytes);
});
});
- describe('readBytes', () => {
+ describe('readBytesWithLength', () => {
it('should successfully read short, nested array of bytes when it takes up the whole array', async () => {
const testBytesOffset = new BigNumber(0);
- const bytes = await libBytes.publicReadBytes.callAsync(shortTestBytes, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(shortTestBytes, testBytesOffset);
return expect(bytes).to.be.equal(shortData);
});
it('should successfully read short, nested array of bytes when it is offset in the array', async () => {
@@ -471,12 +484,12 @@ describe('LibBytes', () => {
const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, shortTestBytesAsBuffer]);
const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength);
- const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(combinedByteArray, testUint256Offset);
return expect(bytes).to.be.equal(shortData);
});
it('should successfully read a nested array of bytes - one word in length - when it takes up the whole array', async () => {
const testBytesOffset = new BigNumber(0);
- const bytes = await libBytes.publicReadBytes.callAsync(wordOfTestBytes, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(wordOfTestBytes, testBytesOffset);
return expect(bytes).to.be.equal(wordOfData);
});
it('should successfully read a nested array of bytes - one word in length - when it is offset in the array', async () => {
@@ -484,12 +497,12 @@ describe('LibBytes', () => {
const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, wordOfTestBytesAsBuffer]);
const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength);
- const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(combinedByteArray, testUint256Offset);
return expect(bytes).to.be.equal(wordOfData);
});
it('should successfully read long, nested array of bytes when it takes up the whole array', async () => {
const testBytesOffset = new BigNumber(0);
- const bytes = await libBytes.publicReadBytes.callAsync(longTestBytes, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(longTestBytes, testBytesOffset);
return expect(bytes).to.be.equal(longData);
});
it('should successfully read long, nested array of bytes when it is offset in the array', async () => {
@@ -497,46 +510,50 @@ describe('LibBytes', () => {
const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, longTestBytesAsBuffer]);
const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength);
- const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(combinedByteArray, testUint256Offset);
return expect(bytes).to.be.equal(longData);
});
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
// The length of the nested array is 32 bytes. By storing less than 32 bytes, a length cannot be read.
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
- libBytes.publicReadBytes.callAsync(byteArrayShorterThan32Bytes, offset),
+ libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if we store a nested byte array length, without a nested byte array', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
- libBytes.publicReadBytes.callAsync(testBytes32, offset),
+ libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset),
RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength);
return expectRevertOrOtherErrorAsync(
- libBytes.publicReadBytes.callAsync(byteArrayShorterThan32Bytes, badOffset),
+ libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset),
RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectRevertOrOtherErrorAsync(
- libBytes.publicReadBytes.callAsync(testBytes32, badOffset),
+ libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset),
RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
- describe('writeBytes', () => {
+ describe('writeBytesWithLength', () => {
it('should successfully write short, nested array of bytes when it takes up the whole array)', async () => {
const testBytesOffset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength));
- const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, shortData);
- const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ testBytesOffset,
+ shortData,
+ );
+ const bytesRead = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytesRead).to.be.equal(shortData);
});
it('should successfully write short, nested array of bytes when it is offset in the array', async () => {
@@ -547,19 +564,31 @@ describe('LibBytes', () => {
const emptyByteArray = ethUtil.bufferToHex(
new Buffer(prefixDataAsBuffer.byteLength + shortTestBytesAsBuffer.byteLength),
);
- let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData);
+ let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ prefixOffset,
+ prefixData,
+ );
// Write data after prefix
const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength);
- bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, shortData);
+ bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ bytesWritten,
+ testBytesOffset,
+ shortData,
+ );
// Read data after prefix and validate
- const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytes).to.be.equal(shortData);
});
it('should successfully write a nested array of bytes - one word in length - when it takes up the whole array', async () => {
const testBytesOffset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(wordOfTestBytesAsBuffer.byteLength));
- const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, wordOfData);
- const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ testBytesOffset,
+ wordOfData,
+ );
+ const bytesRead = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytesRead).to.be.equal(wordOfData);
});
it('should successfully write a nested array of bytes - one word in length - when it is offset in the array', async () => {
@@ -570,19 +599,31 @@ describe('LibBytes', () => {
const emptyByteArray = ethUtil.bufferToHex(
new Buffer(prefixDataAsBuffer.byteLength + wordOfTestBytesAsBuffer.byteLength),
);
- let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData);
+ let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ prefixOffset,
+ prefixData,
+ );
// Write data after prefix
const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength);
- bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, wordOfData);
+ bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ bytesWritten,
+ testBytesOffset,
+ wordOfData,
+ );
// Read data after prefix and validate
- const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytes).to.be.equal(wordOfData);
});
it('should successfully write a long, nested bytes when it takes up the whole array', async () => {
const testBytesOffset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(longTestBytesAsBuffer.byteLength));
- const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, longData);
- const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ testBytesOffset,
+ longData,
+ );
+ const bytesRead = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytesRead).to.be.equal(longData);
});
it('should successfully write long, nested array of bytes when it is offset in the array', async () => {
@@ -593,19 +634,23 @@ describe('LibBytes', () => {
const emptyByteArray = ethUtil.bufferToHex(
new Buffer(prefixDataAsBuffer.byteLength + longTestBytesAsBuffer.byteLength),
);
- let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData);
+ let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(
+ emptyByteArray,
+ prefixOffset,
+ prefixData,
+ );
// Write data after prefix
const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength);
- bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, longData);
+ bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync(bytesWritten, testBytesOffset, longData);
// Read data after prefix and validate
- const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset);
+ const bytes = await libBytes.publicReadBytesWithLength.callAsync(bytesWritten, testBytesOffset);
return expect(bytes).to.be.equal(longData);
});
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
const offset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(1));
return expectRevertOrOtherErrorAsync(
- libBytes.publicWriteBytes.callAsync(emptyByteArray, offset, longData),
+ libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData),
RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
@@ -613,10 +658,176 @@ describe('LibBytes', () => {
const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength));
const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength);
return expectRevertOrOtherErrorAsync(
- libBytes.publicWriteBytes.callAsync(emptyByteArray, badOffset, shortData),
+ libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData),
RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
});
+
+ describe('memCopy', () => {
+ // Create memory 0x000102...FF
+ const memSize = 256;
+ // tslint:disable:no-shadowed-variable
+ const memory = new Uint8Array(memSize).map((_, i) => i);
+ const memHex = toHex(memory);
+
+ // Reference implementation to test against
+ const refMemcpy = (mem: Uint8Array, dest: number, source: number, length: number): Uint8Array =>
+ Uint8Array.from(mem).copyWithin(dest, source, source + length);
+
+ // Test vectors: destination, source, length, job description
+ type Tests = Array<[number, number, number, string]>;
+
+ const test = (tests: Tests) =>
+ tests.forEach(([dest, source, length, job]) =>
+ it(job, async () => {
+ const expected = refMemcpy(memory, dest, source, length);
+ const resultStr = await libBytes.testMemcpy.callAsync(
+ memHex,
+ new BigNumber(dest),
+ new BigNumber(source),
+ new BigNumber(length),
+ );
+ const result = fromHex(resultStr);
+ expect(result).to.deep.equal(expected);
+ }),
+ );
+
+ test([[0, 0, 0, 'copies zero bytes with overlap']]);
+
+ describe('copies forward', () =>
+ test([
+ [128, 0, 0, 'zero bytes'],
+ [128, 0, 1, 'one byte'],
+ [128, 0, 11, 'eleven bytes'],
+ [128, 0, 31, 'thirty-one bytes'],
+ [128, 0, 32, 'one word'],
+ [128, 0, 64, 'two words'],
+ [128, 0, 96, 'three words'],
+ [128, 0, 33, 'one word and one byte'],
+ [128, 0, 72, 'two words and eight bytes'],
+ [128, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward within one word', () =>
+ test([
+ [16, 0, 0, 'zero bytes'],
+ [16, 0, 1, 'one byte'],
+ [16, 0, 11, 'eleven bytes'],
+ [16, 0, 16, 'sixteen bytes'],
+ ]));
+
+ describe('copies forward with one byte overlap', () =>
+ test([
+ [0, 0, 1, 'one byte'],
+ [10, 0, 11, 'eleven bytes'],
+ [30, 0, 31, 'thirty-one bytes'],
+ [31, 0, 32, 'one word'],
+ [32, 0, 33, 'one word and one byte'],
+ [71, 0, 72, 'two words and eight bytes'],
+ [99, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward with thirty-one bytes overlap', () =>
+ test([
+ [0, 0, 31, 'thirty-one bytes'],
+ [1, 0, 32, 'one word'],
+ [2, 0, 33, 'one word and one byte'],
+ [41, 0, 72, 'two words and eight bytes'],
+ [69, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward with one word overlap', () =>
+ test([
+ [0, 0, 32, 'one word'],
+ [1, 0, 33, 'one word and one byte'],
+ [41, 0, 72, 'two words and eight bytes'],
+ [69, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward with one word and one byte overlap', () =>
+ test([
+ [0, 0, 33, 'one word and one byte'],
+ [40, 0, 72, 'two words and eight bytes'],
+ [68, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward with two words overlap', () =>
+ test([
+ [0, 0, 64, 'two words'],
+ [8, 0, 72, 'two words and eight bytes'],
+ [36, 0, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward within one word and one byte overlap', () =>
+ test([[0, 0, 1, 'one byte'], [10, 0, 11, 'eleven bytes'], [15, 0, 16, 'sixteen bytes']]));
+
+ describe('copies backward', () =>
+ test([
+ [0, 128, 0, 'zero bytes'],
+ [0, 128, 1, 'one byte'],
+ [0, 128, 11, 'eleven bytes'],
+ [0, 128, 31, 'thirty-one bytes'],
+ [0, 128, 32, 'one word'],
+ [0, 128, 64, 'two words'],
+ [0, 128, 96, 'three words'],
+ [0, 128, 33, 'one word and one byte'],
+ [0, 128, 72, 'two words and eight bytes'],
+ [0, 128, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies backward within one word', () =>
+ test([
+ [0, 16, 0, 'zero bytes'],
+ [0, 16, 1, 'one byte'],
+ [0, 16, 11, 'eleven bytes'],
+ [0, 16, 16, 'sixteen bytes'],
+ ]));
+
+ describe('copies backward with one byte overlap', () =>
+ test([
+ [0, 0, 1, 'one byte'],
+ [0, 10, 11, 'eleven bytes'],
+ [0, 30, 31, 'thirty-one bytes'],
+ [0, 31, 32, 'one word'],
+ [0, 32, 33, 'one word and one byte'],
+ [0, 71, 72, 'two words and eight bytes'],
+ [0, 99, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies backward with thirty-one bytes overlap', () =>
+ test([
+ [0, 0, 31, 'thirty-one bytes'],
+ [0, 1, 32, 'one word'],
+ [0, 2, 33, 'one word and one byte'],
+ [0, 41, 72, 'two words and eight bytes'],
+ [0, 69, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies backward with one word overlap', () =>
+ test([
+ [0, 0, 32, 'one word'],
+ [0, 1, 33, 'one word and one byte'],
+ [0, 41, 72, 'two words and eight bytes'],
+ [0, 69, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies backward with one word and one byte overlap', () =>
+ test([
+ [0, 0, 33, 'one word and one byte'],
+ [0, 40, 72, 'two words and eight bytes'],
+ [0, 68, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies backward with two words overlap', () =>
+ test([
+ [0, 0, 64, 'two words'],
+ [0, 8, 72, 'two words and eight bytes'],
+ [0, 36, 100, 'three words and four bytes'],
+ ]));
+
+ describe('copies forward within one word and one byte overlap', () =>
+ test([[0, 0, 1, 'one byte'], [0, 10, 11, 'eleven bytes'], [0, 15, 16, 'sixteen bytes']]));
+ });
});
// tslint:disable:max-file-line-count
diff --git a/packages/contracts/test/libraries/lib_mem.ts b/packages/contracts/test/libraries/lib_mem.ts
deleted file mode 100644
index 00f7c4d8b..000000000
--- a/packages/contracts/test/libraries/lib_mem.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-import { BigNumber } from '@0xproject/utils';
-import * as chai from 'chai';
-
-import { TestLibMemContract } from '../../src/generated_contract_wrappers/test_lib_mem';
-import { artifacts } from '../../src/utils/artifacts';
-import { chaiSetup } from '../../src/utils/chai_setup';
-import { provider, txDefaults } from '../../src/utils/web3_wrapper';
-
-chaiSetup.configure();
-const expect = chai.expect;
-
-// BUG: Ideally we would use Buffer.from(memory).toString('hex')
-// https://github.com/Microsoft/TypeScript/issues/23155
-const toHex = (buf: Uint8Array): string => buf.reduce((a, v) => a + ('00' + v.toString(16)).slice(-2), '0x');
-
-const fromHex = (str: string): Uint8Array => Uint8Array.from(Buffer.from(str.slice(2), 'hex'));
-
-describe('LibMem', () => {
- let testLibMem: TestLibMemContract;
-
- before(async () => {
- // Deploy TestLibMem
- testLibMem = await TestLibMemContract.deployFrom0xArtifactAsync(artifacts.TestLibMem, provider, txDefaults);
- });
-
- describe('memCopy', () => {
- // Create memory 0x000102...FF
- const memSize = 256;
- const memory = new Uint8Array(memSize).map((_, i) => i);
- const memHex = toHex(memory);
-
- // Reference implementation to test against
- const refMemcpy = (_mem: Uint8Array, dest: number, source: number, length: number): Uint8Array =>
- Uint8Array.from(memory).copyWithin(dest, source, source + length);
-
- // Test vectors: destination, source, length, job description
- type Tests = Array<[number, number, number, string]>;
-
- const test = (tests: Tests) =>
- tests.forEach(([dest, source, length, job]) =>
- it(job, async () => {
- const expected = refMemcpy(memory, dest, source, length);
- const resultStr = await testLibMem.testMemcpy.callAsync(
- memHex,
- new BigNumber(dest),
- new BigNumber(source),
- new BigNumber(length),
- );
- const result = fromHex(resultStr);
- expect(result).to.deep.equal(expected);
- }),
- );
-
- test([[0, 0, 0, 'copies zero bytes with overlap']]);
-
- describe('copies forward', () =>
- test([
- [128, 0, 0, 'zero bytes'],
- [128, 0, 1, 'one byte'],
- [128, 0, 11, 'eleven bytes'],
- [128, 0, 31, 'thirty-one bytes'],
- [128, 0, 32, 'one word'],
- [128, 0, 64, 'two words'],
- [128, 0, 96, 'three words'],
- [128, 0, 33, 'one word and one byte'],
- [128, 0, 72, 'two words and eight bytes'],
- [128, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward within one word', () =>
- test([
- [16, 0, 0, 'zero bytes'],
- [16, 0, 1, 'one byte'],
- [16, 0, 11, 'eleven bytes'],
- [16, 0, 16, 'sixteen bytes'],
- ]));
-
- describe('copies forward with one byte overlap', () =>
- test([
- [0, 0, 1, 'one byte'],
- [10, 0, 11, 'eleven bytes'],
- [30, 0, 31, 'thirty-one bytes'],
- [31, 0, 32, 'one word'],
- [32, 0, 33, 'one word and one byte'],
- [71, 0, 72, 'two words and eight bytes'],
- [99, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward with thirty-one bytes overlap', () =>
- test([
- [0, 0, 31, 'thirty-one bytes'],
- [1, 0, 32, 'one word'],
- [2, 0, 33, 'one word and one byte'],
- [41, 0, 72, 'two words and eight bytes'],
- [69, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward with one word overlap', () =>
- test([
- [0, 0, 32, 'one word'],
- [1, 0, 33, 'one word and one byte'],
- [41, 0, 72, 'two words and eight bytes'],
- [69, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward with one word and one byte overlap', () =>
- test([
- [0, 0, 33, 'one word and one byte'],
- [40, 0, 72, 'two words and eight bytes'],
- [68, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward with two words overlap', () =>
- test([
- [0, 0, 64, 'two words'],
- [8, 0, 72, 'two words and eight bytes'],
- [36, 0, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward within one word and one byte overlap', () =>
- test([[0, 0, 1, 'one byte'], [10, 0, 11, 'eleven bytes'], [15, 0, 16, 'sixteen bytes']]));
-
- describe('copies backward', () =>
- test([
- [0, 128, 0, 'zero bytes'],
- [0, 128, 1, 'one byte'],
- [0, 128, 11, 'eleven bytes'],
- [0, 128, 31, 'thirty-one bytes'],
- [0, 128, 32, 'one word'],
- [0, 128, 64, 'two words'],
- [0, 128, 96, 'three words'],
- [0, 128, 33, 'one word and one byte'],
- [0, 128, 72, 'two words and eight bytes'],
- [0, 128, 100, 'three words and four bytes'],
- ]));
-
- describe('copies backward within one word', () =>
- test([
- [0, 16, 0, 'zero bytes'],
- [0, 16, 1, 'one byte'],
- [0, 16, 11, 'eleven bytes'],
- [0, 16, 16, 'sixteen bytes'],
- ]));
-
- describe('copies backward with one byte overlap', () =>
- test([
- [0, 0, 1, 'one byte'],
- [0, 10, 11, 'eleven bytes'],
- [0, 30, 31, 'thirty-one bytes'],
- [0, 31, 32, 'one word'],
- [0, 32, 33, 'one word and one byte'],
- [0, 71, 72, 'two words and eight bytes'],
- [0, 99, 100, 'three words and four bytes'],
- ]));
-
- describe('copies backward with thirty-one bytes overlap', () =>
- test([
- [0, 0, 31, 'thirty-one bytes'],
- [0, 1, 32, 'one word'],
- [0, 2, 33, 'one word and one byte'],
- [0, 41, 72, 'two words and eight bytes'],
- [0, 69, 100, 'three words and four bytes'],
- ]));
-
- describe('copies backward with one word overlap', () =>
- test([
- [0, 0, 32, 'one word'],
- [0, 1, 33, 'one word and one byte'],
- [0, 41, 72, 'two words and eight bytes'],
- [0, 69, 100, 'three words and four bytes'],
- ]));
-
- describe('copies backward with one word and one byte overlap', () =>
- test([
- [0, 0, 33, 'one word and one byte'],
- [0, 40, 72, 'two words and eight bytes'],
- [0, 68, 100, 'three words and four bytes'],
- ]));
-
- describe('copies backward with two words overlap', () =>
- test([
- [0, 0, 64, 'two words'],
- [0, 8, 72, 'two words and eight bytes'],
- [0, 36, 100, 'three words and four bytes'],
- ]));
-
- describe('copies forward within one word and one byte overlap', () =>
- test([[0, 0, 1, 'one byte'], [0, 10, 11, 'eleven bytes'], [0, 15, 16, 'sixteen bytes']]));
- });
-});