aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-06-25 17:45:17 +0800
committerFabio Berger <me@fabioberger.com>2018-06-25 17:45:17 +0800
commitdf79fb19aff1aa1ed7b1346feccfa3d235c25ea0 (patch)
treed19445672189e3e7a9927dde7ebaa4734493139d
parent2f6f815d81ff0f36a35a31c40bff7df1a974a1af (diff)
parentf8bde5ab9b8e5d4ec8b9532dfbf18d1202dbfb29 (diff)
downloaddexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar.gz
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar.bz2
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar.lz
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar.xz
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.tar.zst
dexon-sol-tools-df79fb19aff1aa1ed7b1346feccfa3d235c25ea0.zip
Merge branch 'v2-prototype' into refactor/check-revert-reasons
* v2-prototype: (48 commits) Fix typos in comments Add modifier and tests for removeAuthorizedAddressAtIndex Update and add tests Change removeAuthorizedAddress => removeAuthorizedAddressAtIndex Move isFunctionRemoveAuthorizedAddress to test Fix usage of `popLastByte` Fix LibBytes is a library Remove `areBytesEqual` Fix usage of `contentAddress()` Clean low bits in bytes4 Clean high bits in address Refactor LibBytes.readBytes4 for consistency Fix LibBytes.equals Add trailing garbage testcase for LibBytes.equals Rename bytes.equals Add slice and sliceDestructive Rename bytes.rawAddress and add bytes.contentAddress Rename read/writeBytesWithLength Using LibBytes for bytes Make LibBytes a library ... # Conflicts: # packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol # packages/contracts/test/libraries/lib_bytes.ts
-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
-rw-r--r--packages/metacoin/package.json6
-rw-r--r--packages/metacoin/tsconfig.json13
-rw-r--r--packages/order-utils/test/order_hash_test.ts12
-rw-r--r--packages/website/ts/components/generate_order/asset_picker.tsx27
-rw-r--r--packages/website/ts/components/onboarding/onboarding_flow.tsx6
-rw-r--r--packages/website/ts/components/portal/drawer_menu.tsx2
-rw-r--r--packages/website/ts/components/portal/portal.tsx138
-rw-r--r--packages/website/ts/components/portal/text_header.tsx15
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx56
-rw-r--r--packages/website/ts/components/relayer_index/relayer_index.tsx40
-rw-r--r--packages/website/ts/components/relayer_index/relayer_top_tokens.tsx7
-rw-r--r--packages/website/ts/components/ui/animation.tsx6
-rw-r--r--packages/website/ts/components/ui/container.tsx1
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx34
-rw-r--r--packages/website/ts/pages/about/about.tsx31
-rw-r--r--packages/website/ts/pages/about/profile.tsx1
-rw-r--r--packages/website/ts/pages/jobs/open_positions.tsx3
-rw-r--r--packages/website/ts/redux/reducer.ts8
-rw-r--r--packages/website/ts/style/z_index.ts1
-rw-r--r--packages/website/ts/utils/utils.ts6
46 files changed, 1178 insertions, 908 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']]));
- });
-});
diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json
index 61ee8280e..6b6ab9c40 100644
--- a/packages/metacoin/package.json
+++ b/packages/metacoin/package.json
@@ -36,11 +36,15 @@
"@0xproject/subproviders": "^0.10.4",
"@0xproject/tslint-config": "^0.4.20",
"@0xproject/types": "^0.8.1",
+ "@0xproject/typescript-typings": "^0.4.1",
"@0xproject/utils": "^0.7.1",
"@0xproject/web3-wrapper": "^0.7.1",
- "ethereum-types": "^0.0.1",
+ "@types/mocha": "^5.2.2",
+ "copyfiles": "^2.0.0",
+ "ethereum-types": "^0.0.2",
"ethers": "3.0.22",
"lodash": "^4.17.4",
+ "run-s": "^0.0.0",
"web3-provider-engine": "^14.0.4"
},
"devDependencies": {
diff --git a/packages/metacoin/tsconfig.json b/packages/metacoin/tsconfig.json
index 8b4cd47a2..bc3bd1e94 100644
--- a/packages/metacoin/tsconfig.json
+++ b/packages/metacoin/tsconfig.json
@@ -1,7 +1,16 @@
{
- "extends": "../../tsconfig",
"compilerOptions": {
- "outDir": "lib"
+ "outDir": "lib",
+ "lib": ["es2017"],
+ "skipLibCheck": true,
+ "typeRoots": [
+ "comment: for building within 0x-monorepo:",
+ "../../node_modules/@0xproject/typescript-typings/types",
+ "../../node_modules/@types",
+ "comment: for building in an isolated environment:",
+ "node_modules/@0xproject/typescript-typings/types",
+ "node_modules/@types"
+ ]
},
"include": ["src/**/*", "test/**/*"]
}
diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts
index 28bfcbafc..0a6be83d0 100644
--- a/packages/order-utils/test/order_hash_test.ts
+++ b/packages/order-utils/test/order_hash_test.ts
@@ -12,8 +12,8 @@ const expect = chai.expect;
describe('Order hashing', () => {
describe('#getOrderHashHex', () => {
- const expectedOrderHash = '0x367ad7730eb8b5feab8a9c9f47c6fcba77a2d4df125ee6a59cc26ac955710f7e';
- const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
+ const expectedOrderHash = '0x434c6b41e2fb6dfcfe1b45c4492fb03700798e9c1afc6f801ba6203f948c1fa7';
+ const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
const order: Order = {
makerAddress: constants.NULL_ADDRESS,
takerAddress: constants.NULL_ADDRESS,
@@ -29,15 +29,11 @@ describe('Order hashing', () => {
takerAssetAmount: new BigNumber(0),
expirationTimeSeconds: new BigNumber(0),
};
- // HACK: Temporarily disable these tests until @dekz has time to fix.
- // This allows us to get all tests running on CI immediately
- it.skip('calculates the order hash', async () => {
+ it('calculates the order hash', async () => {
const orderHash = orderHashUtils.getOrderHashHex(order);
expect(orderHash).to.be.equal(expectedOrderHash);
});
- // HACK: Temporarily disable these tests until @dekz has time to fix.
- // This allows us to get all tests running on CI immediately
- it.skip('throws a readable error message if taker format is invalid', async () => {
+ it('throws a readable error message if taker format is invalid', async () => {
const orderWithInvalidtakerFormat = {
...order,
takerAddress: (null as any) as string,
diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx
index b43ac1f2e..b0dcf5678 100644
--- a/packages/website/ts/components/generate_order/asset_picker.tsx
+++ b/packages/website/ts/components/generate_order/asset_picker.tsx
@@ -9,6 +9,7 @@ import { TokenIcon } from 'ts/components/ui/token_icon';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types';
+import { constants } from 'ts/utils/constants';
const TOKEN_ICON_DIMENSION = 100;
const TILE_DIMENSION = 146;
@@ -134,7 +135,9 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => {
if (
(this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) ||
- (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)
+ (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) ||
+ token.symbol === constants.ZRX_TOKEN_SYMBOL ||
+ token.symbol === constants.ETHER_TOKEN_SYMBOL
) {
return null; // Skip
}
@@ -232,12 +235,14 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
this.props.onTokenChosen(newToken.address);
}
private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean): Promise<void> {
+ const resetState: AssetPickerState = {
+ ...this.state,
+ isAddingTokenToTracked: false,
+ assetView: AssetViews.ASSET_PICKER,
+ chosenTrackTokenAddress: undefined,
+ };
if (!didUserAcceptTracking) {
- this.setState({
- isAddingTokenToTracked: false,
- assetView: AssetViews.ASSET_PICKER,
- chosenTrackTokenAddress: undefined,
- });
+ this.setState(resetState);
this._onCloseDialog();
return;
}
@@ -246,6 +251,10 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
});
const tokenAddress = this.state.chosenTrackTokenAddress;
const token = this.props.tokenByAddress[tokenAddress];
+ if (_.isUndefined(tokenAddress)) {
+ this.setState(resetState);
+ return;
+ }
const newTokenEntry = {
...token,
};
@@ -254,11 +263,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
- this.setState({
- isAddingTokenToTracked: false,
- assetView: AssetViews.ASSET_PICKER,
- chosenTrackTokenAddress: undefined,
- });
+ this.setState(resetState);
this.props.onTokenChosen(tokenAddress);
}
}
diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx
index 331899469..ec8d96191 100644
--- a/packages/website/ts/components/onboarding/onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx
@@ -42,7 +42,11 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>;
} else {
onboardingElement = (
- <Popper referenceElement={this._getElementForStep()} placement={this._getCurrentStep().placement}>
+ <Popper
+ referenceElement={this._getElementForStep()}
+ placement={this._getCurrentStep().placement}
+ positionFixed={true}
+ >
{this._renderPopperChildren.bind(this)}
</Popper>
);
diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx
index 4bd07769f..205a60afc 100644
--- a/packages/website/ts/components/portal/drawer_menu.tsx
+++ b/packages/website/ts/components/portal/drawer_menu.tsx
@@ -65,7 +65,7 @@ interface HeaderProps {
const Header = (props: HeaderProps) => {
return (
<div className="flex flex-center py4">
- <div className="flex flex-column mx-auto">
+ <div className="flex flex-column mx-auto items-center">
<Identicon address={props.userAddress} diameter={IDENTICON_DIAMETER} style={styles.identicon} />
<Text className="pt2" fontColor={colors.white}>
{props.displayMessage}
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 11b3b43f4..4166fde53 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -1,4 +1,4 @@
-import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared';
+import { colors, constants as sharedConstants } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
@@ -107,26 +107,7 @@ const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded);
const LEFT_COLUMN_WIDTH = 346;
const MENU_PADDING_LEFT = 185;
const LARGE_LAYOUT_MAX_WIDTH = 1200;
-
-const styles: Styles = {
- root: {
- width: '100%',
- height: '100%',
- backgroundColor: colors.lightestGrey,
- },
- body: {
- height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
- },
- leftColumn: {
- width: LEFT_COLUMN_WIDTH,
- height: '100%',
- },
- scrollContainer: {
- height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
- WebkitOverflowScrolling: 'touch',
- overflow: 'auto',
- },
-};
+const LARGE_LAYOUT_MARGIN = 30;
export class Portal extends React.Component<PortalProps, PortalState> {
private _blockchain: Blockchain;
@@ -245,7 +226,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
? TokenVisibility.UNTRACKED
: TokenVisibility.TRACKED;
return (
- <div style={styles.root}>
+ <Container>
<DocumentTitle title="0x Portal DApp" />
<TopBar
userAddress={this.props.userAddress}
@@ -259,10 +240,14 @@ export class Portal extends React.Component<PortalProps, PortalState> {
blockchain={this._blockchain}
translate={this.props.translate}
displayType={TopBarDisplayType.Expanded}
- style={{ backgroundColor: colors.lightestGrey }}
+ style={{
+ backgroundColor: colors.lightestGrey,
+ position: 'fixed',
+ zIndex: zIndex.topBar,
+ }}
maxWidth={LARGE_LAYOUT_MAX_WIDTH}
/>
- <div id="portal" style={styles.body}>
+ <Container marginTop={TOP_BAR_HEIGHT} minHeight="100vh" backgroundColor={colors.lightestGrey}>
<Switch>
<Route path={`${WebsitePaths.Portal}/:route`} render={this._renderOtherRoutes.bind(this)} />
<Route
@@ -301,13 +286,8 @@ export class Portal extends React.Component<PortalProps, PortalState> {
tokenByAddress={this.props.tokenByAddress}
tokenVisibility={tokenVisibility}
/>
- </div>
- <PortalOnboardingFlow
- blockchain={this._blockchain}
- trackedTokenStateByAddress={this.state.trackedTokenStateByAddress}
- refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
- />
- </div>
+ </Container>
+ </Container>
);
}
private _renderMainRoute(): React.ReactNode {
@@ -341,41 +321,48 @@ export class Portal extends React.Component<PortalProps, PortalState> {
}
private _renderWallet(): React.ReactNode {
const startOnboarding = this._renderStartOnboarding();
- const isMobile = this.props.screenWidth === ScreenWidths.Sm;
+ const isMobile = utils.isMobile(this.props.screenWidth);
// We need room to scroll down for mobile onboarding
const marginBottom = isMobile ? '200px' : '15px';
return (
<div>
- {isMobile && <Container marginBottom="15px">{startOnboarding}</Container>}
- <Container marginBottom={marginBottom}>
- <Wallet
- style={
- !isMobile && this.props.isPortalOnboardingShowing
- ? { zIndex: zIndex.aboveOverlay, position: 'relative' }
- : undefined
- }
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this._blockchain}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- blockchainErr={this.props.blockchainErr}
- dispatcher={this.props.dispatcher}
- tokenByAddress={this.props.tokenByAddress}
- trackedTokens={this._getCurrentTrackedTokens()}
- userEtherBalanceInWei={this.props.userEtherBalanceInWei}
- lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
- injectedProviderName={this.props.injectedProviderName}
- providerType={this.props.providerType}
- screenWidth={this.props.screenWidth}
- location={this.props.location}
- trackedTokenStateByAddress={this.state.trackedTokenStateByAddress}
- onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
- onAddToken={this._onAddToken.bind(this)}
- onRemoveToken={this._onRemoveToken.bind(this)}
- refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
- />
+ <Container>
+ {isMobile && <Container marginBottom="15px">{startOnboarding}</Container>}
+ <Container marginBottom={marginBottom}>
+ <Wallet
+ style={
+ !isMobile && this.props.isPortalOnboardingShowing
+ ? { zIndex: zIndex.aboveOverlay, position: 'relative' }
+ : undefined
+ }
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ tokenByAddress={this.props.tokenByAddress}
+ trackedTokens={this._getCurrentTrackedTokens()}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ screenWidth={this.props.screenWidth}
+ location={this.props.location}
+ trackedTokenStateByAddress={this.state.trackedTokenStateByAddress}
+ onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
+ onAddToken={this._onAddToken.bind(this)}
+ onRemoveToken={this._onRemoveToken.bind(this)}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
+ />
+ </Container>
+ {!isMobile && <Container marginTop="15px">{startOnboarding}</Container>}
</Container>
- {!isMobile && <Container marginTop="15px">{startOnboarding}</Container>}
+ <PortalOnboardingFlow
+ blockchain={this._blockchain}
+ trackedTokenStateByAddress={this.state.trackedTokenStateByAddress}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
+ />
</div>
);
}
@@ -551,7 +538,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
private _renderRelayerIndexSection(): React.ReactNode {
return (
<Section
- header={<TextHeader labelText="Explore 0x Relayers" />}
+ header={<TextHeader labelText="0x Relayers" />}
body={<RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />}
/>
);
@@ -708,14 +695,23 @@ interface LargeLayoutProps {
}
const LargeLayout = (props: LargeLayoutProps) => {
return (
- <div className="mx-auto flex flex-center" style={{ maxWidth: LARGE_LAYOUT_MAX_WIDTH }}>
- <div className="flex-last px2">
- <div style={styles.leftColumn}>{props.left}</div>
- </div>
- <div className="flex-auto px2" style={styles.scrollContainer}>
- {props.right}
+ <Container className="mx-auto flex flex-center" maxWidth={LARGE_LAYOUT_MAX_WIDTH}>
+ <div className="flex-last">
+ <Container
+ width={LEFT_COLUMN_WIDTH}
+ position="fixed"
+ zIndex={zIndex.aboveTopBar}
+ marginLeft={LARGE_LAYOUT_MARGIN}
+ >
+ {props.left}
+ </Container>
</div>
- </div>
+ <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH + LARGE_LAYOUT_MARGIN}>
+ <Container className="flex-auto" marginLeft={LARGE_LAYOUT_MARGIN} marginRight={LARGE_LAYOUT_MARGIN}>
+ {props.right}
+ </Container>
+ </Container>
+ </Container>
);
};
@@ -725,9 +721,7 @@ interface SmallLayoutProps {
const SmallLayout = (props: SmallLayoutProps) => {
return (
<div className="flex flex-center">
- <div className="flex-auto px3" style={styles.scrollContainer}>
- {props.content}
- </div>
+ <div className="flex-auto px3">{props.content}</div>
</div>
);
}; // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/portal/text_header.tsx b/packages/website/ts/components/portal/text_header.tsx
index 4aabd47d0..853da3a29 100644
--- a/packages/website/ts/components/portal/text_header.tsx
+++ b/packages/website/ts/components/portal/text_header.tsx
@@ -1,21 +1,16 @@
-import { Styles } from '@0xproject/react-shared';
+import { colors } from '@0xproject/react-shared';
import * as React from 'react';
+import { Text } from 'ts/components/ui/text';
+
export interface TextHeaderProps {
labelText: string;
}
-const styles: Styles = {
- title: {
- fontWeight: 'bold',
- fontSize: 20,
- },
-};
-
export const TextHeader = (props: TextHeaderProps) => {
return (
- <div className="py3" style={styles.title}>
+ <Text className="pt3 pb2" fontWeight="bold" fontSize="16px" fontColor={colors.darkestGrey}>
{props.labelText}
- </div>
+ </Text>
);
};
diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
index 23860856b..18b069ae2 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -1,6 +1,6 @@
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
-import { GridTile } from 'material-ui/GridList';
+import { GridTile as PlainGridTile } from 'material-ui/GridList';
import * as React from 'react';
import { analytics } from 'ts/utils/analytics';
@@ -9,7 +9,9 @@ import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
import { WebsiteBackendRelayerInfo } from 'ts/types';
+import { utils } from 'ts/utils/utils';
export interface RelayerGridTileProps {
relayerInfo: WebsiteBackendRelayerInfo;
@@ -19,29 +21,23 @@ export interface RelayerGridTileProps {
const styles: Styles = {
root: {
boxSizing: 'border-box',
+ // All material UI components have position: relative
+ // which creates a new stacking context and makes z-index stuff impossible. So reset.
+ position: 'static',
},
innerDiv: {
- padding: 6,
height: '100%',
boxSizing: 'border-box',
},
header: {
height: '50%',
width: '100%',
- borderBottomRightRadius: 4,
- borderBottomLeftRadius: 4,
- borderTopRightRadius: 4,
- borderTopLeftRadius: 4,
- borderWidth: 1,
- borderStyle: 'solid',
- borderColor: colors.walletBorder,
},
body: {
- paddingLeft: 6,
- paddingRight: 6,
height: '50%',
width: '100%',
boxSizing: 'border-box',
+ padding: 12,
},
weeklyTradeVolumeLabel: {
fontSize: 14,
@@ -69,7 +65,10 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId];
const eventLabel = `${props.relayerInfo.name}-${networkName}`;
- const trackRelayerClick = () => analytics.logEvent('Portal', 'Relayer Click', eventLabel);
+ const onClick = () => {
+ analytics.logEvent('Portal', 'Relayer Click', eventLabel);
+ utils.openUrl(link);
+ };
const headerImageUrl = props.relayerInfo.logoImgUrl;
const headerBackgroundColor =
!_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor)
@@ -77,22 +76,17 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
: FALLBACK_PRIMARY_COLOR;
return (
<Island style={styles.root} Component={GridTile}>
- <div style={styles.innerDiv}>
- <a href={link} target="_blank" style={{ textDecoration: 'none' }} onClick={trackRelayerClick}>
- <div
- className="flex items-center"
- style={{ ...styles.header, backgroundColor: headerBackgroundColor }}
- >
- <Image
- className="mx-auto"
- src={props.relayerInfo.logoImgUrl}
- fallbackSrc={FALLBACK_IMG_SRC}
- height={RELAYER_ICON_HEIGHT}
- />
- </div>
- </a>
+ <div style={styles.innerDiv} onClick={onClick}>
+ <div className="flex items-center" style={{ ...styles.header, backgroundColor: headerBackgroundColor }}>
+ <Image
+ className="mx-auto"
+ src={props.relayerInfo.logoImgUrl}
+ fallbackSrc={FALLBACK_IMG_SRC}
+ height={RELAYER_ICON_HEIGHT}
+ />
+ </div>
<div style={styles.body}>
- <div className="py1" style={styles.relayerNameLabel}>
+ <div className="pb1" style={styles.relayerNameLabel}>
{props.relayerInfo.name}
</div>
<Section titleText="Weekly Trade Volume">
@@ -111,6 +105,14 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
);
};
+const GridTile = styled(PlainGridTile)`
+ cursor: pointer;
+ transition: transform 0.2s ease;
+ &:hover {
+ transform: translate(0px, -3px);
+ }
+`;
+
interface SectionProps {
titleText: string;
children?: React.ReactNode;
diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx
index d565eb608..4aea1bbbb 100644
--- a/packages/website/ts/components/relayer_index/relayer_index.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_index.tsx
@@ -1,4 +1,3 @@
-import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
import { GridList } from 'material-ui/GridList';
@@ -6,7 +5,6 @@ import * as React from 'react';
import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile';
import { Retry } from 'ts/components/ui/retry';
-import { colors } from 'ts/style/colors';
import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types';
import { backendClient } from 'ts/utils/backend_client';
@@ -20,22 +18,6 @@ interface RelayerIndexState {
error?: Error;
}
-const styles: Styles = {
- root: {
- width: '100%',
- },
- item: {
- backgroundColor: colors.white,
- borderBottomRightRadius: 10,
- borderBottomLeftRadius: 10,
- borderTopRightRadius: 10,
- borderTopLeftRadius: 10,
- boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
- overflow: 'hidden',
- padding: 4,
- },
-};
-
const CELL_HEIGHT = 290;
const NUMBER_OF_COLUMNS_LARGE = 3;
const NUMBER_OF_COLUMNS_MEDIUM = 2;
@@ -76,18 +58,16 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
} else {
const numberOfColumns = this._numberOfColumnsForScreenWidth(this.props.screenWidth);
return (
- <div style={styles.root}>
- <GridList
- cellHeight={CELL_HEIGHT}
- cols={numberOfColumns}
- padding={GRID_PADDING}
- style={styles.gridList}
- >
- {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => (
- <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} />
- ))}
- </GridList>
- </div>
+ <GridList
+ cellHeight={CELL_HEIGHT}
+ cols={numberOfColumns}
+ padding={GRID_PADDING}
+ style={{ marginTop: -10, marginBottom: 0 }}
+ >
+ {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => (
+ <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} />
+ ))}
+ </GridList>
);
}
}
diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
index b599e7123..f544fc924 100644
--- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
@@ -70,7 +70,10 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
};
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
- const trackTokenClick = () => analytics.logEvent('Portal', 'Token Click', eventLabel);
+ const onClick = (event: React.MouseEvent<HTMLElement>) => {
+ event.stopPropagation();
+ analytics.logEvent('Portal', 'Token Click', eventLabel);
+ };
return (
<a
href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)}
@@ -78,7 +81,7 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
style={style}
onMouseEnter={this._onToggleHover.bind(this, true)}
onMouseLeave={this._onToggleHover.bind(this, false)}
- onClick={trackTokenClick}
+ onClick={onClick}
>
{this.props.tokenInfo.symbol}
</a>
diff --git a/packages/website/ts/components/ui/animation.tsx b/packages/website/ts/components/ui/animation.tsx
index cbda2993d..136f3d005 100644
--- a/packages/website/ts/components/ui/animation.tsx
+++ b/packages/website/ts/components/ui/animation.tsx
@@ -11,13 +11,15 @@ const PlainAnimation: React.StatelessComponent<AnimationProps> = props => <div {
const appearFromBottomFrames = keyframes`
from {
- position: absolute;
+ position: fixed;
bottom: -500px;
+ left: 0px;
}
to {
- position: absolute;
+ position: fixed;
bottom: 0px;
+ left: 0px;
}
`;
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index 90aec0e7c..a747ef01f 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -15,6 +15,7 @@ export interface ContainerProps {
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
width?: StringOrNum;
+ minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;
position?: 'absolute' | 'fixed' | 'relative' | 'unset';
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index f88fd6c24..dc48d6619 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -29,6 +29,7 @@ import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
import {
BlockchainErrs,
ProviderType,
@@ -138,6 +139,12 @@ const USD_DECIMAL_PLACES = 2;
const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
+const ActionButton = styled(FloatingActionButton)`
+ button {
+ position: static !important;
+ }
+`;
+
export class Wallet extends React.Component<WalletProps, WalletState> {
public static defaultProps = {
style: {},
@@ -244,17 +251,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
<ListItem
primaryText={
<div className="flex">
- <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
+ <ActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
<ContentAdd />
- </FloatingActionButton>
- <FloatingActionButton
- mini={true}
- zDepth={0}
- className="px1"
- onClick={this.props.onRemoveToken}
- >
+ </ActionButton>
+ <ActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}>
<ContentRemove />
- </FloatingActionButton>
+ </ActionButton>
<div
style={{
paddingLeft: 10,
@@ -309,7 +311,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
wrappedEtherDirection: Side.Deposit,
};
const key = ETHER_ITEM_KEY;
- return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, 'eth-row');
+ return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, false, 'eth-row');
}
private _renderTokenRows(): React.ReactNode {
const trackedTokens = this.props.trackedTokens;
@@ -320,7 +322,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
}
- private _renderTokenRow(token: Token, _index: number): React.ReactNode {
+ private _renderTokenRow(token: Token, index: number): React.ReactNode {
const tokenState = this.props.trackedTokenStateByAddress[token.address];
if (_.isUndefined(tokenState)) {
return null;
@@ -348,12 +350,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
},
};
const key = token.address;
+ const isLastRow = index === this.props.trackedTokens.length - 1;
return this._renderBalanceRow(
key,
icon,
primaryText,
secondaryText,
accessoryItemConfig,
+ isLastRow,
isWeth ? 'weth-row' : undefined,
);
}
@@ -363,13 +367,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
primaryText: React.ReactNode,
secondaryText: React.ReactNode,
accessoryItemConfig: AccessoryItemConfig,
+ isLastRow: boolean,
className?: string,
): React.ReactNode {
const shouldShowWrapEtherItem =
!_.isUndefined(this.state.wrappedEtherDirection) &&
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection &&
!_.isUndefined(this.props.userEtherBalanceInWei);
- const additionalStyle = shouldShowWrapEtherItem ? walletItemStyles.focusedItem : styles.borderedItem;
+ let additionalStyle;
+ if (shouldShowWrapEtherItem) {
+ additionalStyle = walletItemStyles.focusedItem;
+ } else if (!isLastRow) {
+ additionalStyle = styles.borderedItem;
+ }
const style = { ...styles.tokenItem, ...additionalStyle };
const etherToken = this._getEthToken();
return (
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index 6830b64ab..0259af36f 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -157,25 +157,25 @@ const teamRow5: ProfileInfo[] = [
github: 'http://github.com/fragosti',
},
{
- name: 'Chris Kalani',
- title: 'Director of Design',
- description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
- image: 'images/team/chris.png',
- linkedIn: 'https://www.linkedin.com/in/chriskalani/',
- github: 'https://github.com/chriskalani',
- },
-];
-
-const teamRow6: ProfileInfo[] = [
- {
name: 'Mel Oberto',
- title: 'Office Operations / Executive Assistant',
+ title: 'Office Ops / Executive Assistant',
description: `Daily Operations. Previously People Operations Associate at Heap. Marketing and MBA at Sacred Heart University.`,
image: 'images/team/mel.png',
linkedIn: 'https://www.linkedin.com/in/melanieoberto',
},
];
+// const teamRow6: ProfileInfo[] = [
+// {
+// name: 'Chris Kalani',
+// title: 'Director of Design',
+// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
+// image: 'images/team/chris.png',
+// linkedIn: 'https://www.linkedin.com/in/chriskalani/',
+// github: 'https://github.com/chriskalani',
+// },
+// ];
+
const advisors: ProfileInfo[] = [
{
name: 'Fred Ehrsam',
@@ -259,9 +259,9 @@ export class About extends React.Component<AboutProps, AboutState> {
lineHeight: 1.5,
}}
>
- Our team is a diverse and globally distributed group with backgrounds in engineering,
- research, business and design. We are passionate about decentralized technology and its
- potential to act as an equalizing force in the world.
+ Our team is a globally distributed group with backgrounds in engineering, research, business
+ and design. We are passionate about decentralized technology and its potential to act as an
+ equalizing force in the world.
</div>
</div>
<div className="pt3 md-px4 lg-px0">
@@ -270,7 +270,6 @@ export class About extends React.Component<AboutProps, AboutState> {
<div className="clearfix">{this._renderProfiles(teamRow3)}</div>
<div className="clearfix">{this._renderProfiles(teamRow4)}</div>
<div className="clearfix">{this._renderProfiles(teamRow5)}</div>
- <div className="clearfix">{this._renderProfiles(teamRow6)}</div>
</div>
<div className="pt3 pb2">
<div
diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx
index e73b1f193..bcbeb0272 100644
--- a/packages/website/ts/pages/about/profile.tsx
+++ b/packages/website/ts/pages/about/profile.tsx
@@ -39,6 +39,7 @@ export const Profile = (props: ProfileProps) => {
fontSize: 14,
fontFamily: 'Roboto Mono',
color: colors.darkGrey,
+ whiteSpace: 'nowrap',
}}
>
{props.profileInfo.title.toUpperCase()}
diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx
index f3d980315..e789795c1 100644
--- a/packages/website/ts/pages/jobs/open_positions.tsx
+++ b/packages/website/ts/pages/jobs/open_positions.tsx
@@ -11,6 +11,7 @@ import { colors } from 'ts/style/colors';
import { styled } from 'ts/style/theme';
import { ScreenWidths, WebsiteBackendJobInfo } from 'ts/types';
import { backendClient } from 'ts/utils/backend_client';
+import { utils } from 'ts/utils/utils';
const labelStyle = { fontFamily: 'Roboto Mono', fontSize: 18 };
const HEADER_TEXT = 'Open Positions';
@@ -161,7 +162,7 @@ export class OpenPositions extends React.Component<OpenPositionsProps, OpenPosit
private _openJobInfoUrl(jobInfo: WebsiteBackendJobInfo): void {
const url = jobInfo.url;
- window.open(url, '_blank');
+ utils.openUrl(url);
}
}
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
index 9d3d8f7d9..ed6a4868e 100644
--- a/packages/website/ts/redux/reducer.ts
+++ b/packages/website/ts/redux/reducer.ts
@@ -156,7 +156,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
}
case ActionTypes.AddTokenToTokenByAddress: {
- const newTokenByAddress = state.tokenByAddress;
+ const newTokenByAddress = { ...state.tokenByAddress };
newTokenByAddress[action.data.address] = action.data;
return {
...state,
@@ -165,7 +165,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
}
case ActionTypes.RemoveTokenFromTokenByAddress: {
- const newTokenByAddress = state.tokenByAddress;
+ const newTokenByAddress = { ...state.tokenByAddress };
delete newTokenByAddress[action.data.address];
return {
...state,
@@ -174,7 +174,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
}
case ActionTypes.UpdateTokenByAddress: {
- const tokenByAddress = state.tokenByAddress;
+ const tokenByAddress = { ...state.tokenByAddress };
const tokens = action.data;
_.each(tokens, token => {
const updatedToken = {
@@ -253,7 +253,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
}
case ActionTypes.UpdateChosenAssetTokenAddress: {
- const newAssetToken = state.sideToAssetToken[action.data.side];
+ const newAssetToken = { ...state.sideToAssetToken[action.data.side] };
newAssetToken.address = action.data.address;
const newSideToAssetToken = {
...state.sideToAssetToken,
diff --git a/packages/website/ts/style/z_index.ts b/packages/website/ts/style/z_index.ts
index 0411cdd91..a3998f59b 100644
--- a/packages/website/ts/style/z_index.ts
+++ b/packages/website/ts/style/z_index.ts
@@ -1,5 +1,6 @@
export const zIndex = {
topBar: 1100,
+ aboveTopBar: 1101,
overlay: 1105,
aboveOverlay: 1106,
};
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 0bd3dbcfa..0cb965f05 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -362,4 +362,10 @@ export const utils = {
const formattedAmount = unitAmount.toFixed(precision);
return `${formattedAmount} ${symbol}`;
},
+ openUrl(url: string): void {
+ window.open(url, '_blank');
+ },
+ isMobile(screenWidth: ScreenWidths): boolean {
+ return screenWidth === ScreenWidths.Sm;
+ },
};