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
This commit is contained in:
Fabio Berger 2018-06-25 11:45:17 +02:00
commit df79fb19af
46 changed files with 1162 additions and 892 deletions

View File

@ -30,10 +30,10 @@
"MixinAuthorizable",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"TestAssetProxyOwner",
"TestAssetDataDecoders",
"TestAssetProxyDispatcher",
"TestLibBytes",
"TestLibMem",
"TestLibs",
"TestSignatureValidator",
"TestValidator",

View File

@ -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",

View File

@ -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,7 +87,12 @@ contract MixinAuthorizable is
uint256 index
)
external
onlyOwner
{
require(
authorized[target],
TARGET_NOT_AUTHORIZED
);
require(
index < authorities.length,
INDEX_OUT_OF_BOUNDS

View File

@ -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

View File

@ -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 (

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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
);
}

View File

@ -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)
}
}
}
}
}

View File

@ -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
);
_;
}

View File

@ -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,

View File

@ -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;
}

View File

@ -92,7 +92,6 @@ export enum ContractName {
Arbitrage = 'Arbitrage',
TestAssetDataDecoders = 'TestAssetDataDecoders',
TestAssetProxyDispatcher = 'TestAssetProxyDispatcher',
TestLibMem = 'TestLibMem',
TestLibs = 'TestLibs',
TestSignatureValidator = 'TestSignatureValidator',
ERC20Proxy = 'ERC20Proxy',

View File

@ -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();

View File

@ -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,

View File

@ -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;
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),
);
});
return expectRevertOrAlwaysFailingTransactionAsync(
multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }),
);
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 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;
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;
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
);
});
return expectRevertOrAlwaysFailingTransactionAsync(
multiSig.executeRemoveAuthorizedAddress.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;
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]);
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
);
});
return expectRevertOrAlwaysFailingTransactionAsync(
multiSig.executeRemoveAuthorizedAddress.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;
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]);
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: 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);
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;
const tx = await multiSig.transactions.callAsync(txId);
const isExecuted = tx[3];
expect(isExecuted).to.equal(true);
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
const isAuthorized = await erc20Proxy.authorized.callAsync(authorized);
expect(isAuthorized).to.equal(false);
});
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);
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;
const tx = await testAssetProxyOwner.transactions.callAsync(txId);
const isExecuted = tx[3];
expect(isExecuted).to.equal(true);
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
const isAuthorized = await erc20Proxy.authorized.callAsync(authorized);
expect(isAuthorized).to.equal(false);
});
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);
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;
const tx = await multiSig.transactions.callAsync(txId);
const isExecuted = tx[3];
expect(isExecuted).to.equal(true);
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: 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],
}),
);
});
});
});
});

View File

@ -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

View File

@ -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']]));
});
});

View File

@ -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": {

View File

@ -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/**/*"]
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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>
);

View File

@ -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}

View File

@ -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>
<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 className="flex-auto px2" style={styles.scrollContainer}>
{props.right}
</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

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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>
);
}
}

View File

@ -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>

View File

@ -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;
}
`;

View File

@ -15,6 +15,7 @@ export interface ContainerProps {
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
width?: StringOrNum;
minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;
position?: 'absolute' | 'fixed' | 'relative' | 'unset';

View File

@ -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 (

View File

@ -156,26 +156,26 @@ const teamRow5: ProfileInfo[] = [
linkedIn: 'https://www.linkedin.com/in/fragosti/',
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

View File

@ -39,6 +39,7 @@ export const Profile = (props: ProfileProps) => {
fontSize: 14,
fontFamily: 'Roboto Mono',
color: colors.darkGrey,
whiteSpace: 'nowrap',
}}
>
{props.profileInfo.title.toUpperCase()}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -1,5 +1,6 @@
export const zIndex = {
topBar: 1100,
aboveTopBar: 1101,
overlay: 1105,
aboveOverlay: 1106,
};

View File

@ -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;
},
};