diff options
36 files changed, 500 insertions, 284 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 8539342b7..c227d210f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -225,8 +225,9 @@ contract MixinExchangeCore is // Log order emit Fill( order.makerAddress, - takerAddress, order.feeRecipientAddress, + takerAddress, + msg.sender, fillResults.makerAssetFilledAmount, fillResults.takerAssetFilledAmount, fillResults.makerFeePaid, @@ -255,6 +256,7 @@ contract MixinExchangeCore is emit Cancel( order.makerAddress, order.feeRecipientAddress, + msg.sender, orderHash, order.makerAssetData, order.takerAssetData @@ -310,7 +312,11 @@ contract MixinExchangeCore is // Validate Maker signature (check only if first time seen) if (orderInfo.orderTakerAssetFilledAmount == 0) { require( - isValidSignature(orderInfo.orderHash, order.makerAddress, signature), + isValidSignature( + orderInfo.orderHash, + order.makerAddress, + signature + ), INVALID_ORDER_SIGNATURE ); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol index 4a2beff57..881d6e7b3 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol @@ -43,43 +43,52 @@ contract MixinSignatureValidator is /// @dev Approves a hash on-chain using any valid signature type. /// After presigning a hash, the preSign signature type will become valid for that hash and signer. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof that the hash has been signed by signer. function preSign( bytes32 hash, - address signer, + address signerAddress, bytes signature ) external { require( - isValidSignature(hash, signer, signature), + isValidSignature( + hash, + signerAddress, + signature + ), INVALID_SIGNATURE ); - preSigned[hash][signer] = true; + preSigned[hash][signerAddress] = true; } /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. - /// @param validator Address of Validator contract. + /// @param validatorAddress Address of Validator contract. /// @param approval Approval or disapproval of Validator contract. function setSignatureValidatorApproval( - address validator, + address validatorAddress, bool approval ) external { - address signer = getCurrentContextAddress(); - allowedValidators[signer][validator] = approval; + address signerAddress = getCurrentContextAddress(); + allowedValidators[signerAddress][validatorAddress] = approval; + emit SignatureValidatorApproval( + signerAddress, + validatorAddress, + approval + ); } /// @dev Verifies that a hash has been signed by the given signer. /// @param hash Any 32 byte hash. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof that the hash has been signed by signer. /// @return True if the address recovered from the provided signature matches the input signer address. function isValidSignature( bytes32 hash, - address signer, + address signerAddress, bytes memory signature ) public @@ -138,7 +147,7 @@ contract MixinSignatureValidator is r = readBytes32(signature, 1); s = readBytes32(signature, 33); recovered = ecrecover(hash, v, r, s); - isValid = signer == recovered; + isValid = signerAddress == recovered; return isValid; // Signed using web3.eth_sign @@ -156,7 +165,7 @@ contract MixinSignatureValidator is r, s ); - isValid = signer == recovered; + isValid = signerAddress == recovered; return isValid; // Implicitly signed by caller. @@ -172,13 +181,13 @@ contract MixinSignatureValidator is signature.length == 0, LENGTH_0_REQUIRED ); - isValid = signer == msg.sender; + isValid = signerAddress == msg.sender; return isValid; // Signature verified by wallet contract. // If used with an order, the maker of the order is the wallet contract. } else if (signatureType == SignatureType.Wallet) { - isValid = IWallet(signer).isValidSignature(hash, signature); + isValid = IWallet(signerAddress).isValidSignature(hash, signature); return isValid; // Signature verified by validator contract. @@ -190,21 +199,21 @@ 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 validator = popLast20Bytes(signature); + address validatorAddress = popLast20Bytes(signature); // Ensure signer has approved validator. - if (!allowedValidators[signer][validator]) { + if (!allowedValidators[signerAddress][validatorAddress]) { return false; } - isValid = IValidator(validator).isValidSignature( + isValid = IValidator(validatorAddress).isValidSignature( hash, - signer, + signerAddress, signature ); return isValid; // Signer signed hash previously using the preSign function. } else if (signatureType == SignatureType.PreSigned) { - isValid = preSigned[hash][signer]; + isValid = preSigned[hash][signerAddress]; return isValid; // Signature from Trezor hardware wallet. @@ -229,12 +238,7 @@ contract MixinSignatureValidator is r, s ); - isValid = signer == recovered; - return isValid; - - // Signer signed hash previously using the preSign function - } else if (signatureType == SignatureType.PreSigned) { - isValid = preSigned[hash][signer]; + isValid = signerAddress == recovered; return isValid; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol index f1332363c..d3d22d48f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -41,17 +41,17 @@ contract MixinTransactions is bytes32 constant EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked( "ZeroExTransaction(", "uint256 salt,", - "address signer,", + "address signerAddress,", "bytes data", ")" )); /// @dev Calculates EIP712 hash of the Transaction. /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signer Address of transaction signer. + /// @param signerAddress Address of transaction signer. /// @param data AbiV2 encoded calldata. /// @return EIP712 hash of the Transaction. - function hashZeroExTransaction(uint256 salt, address signer, bytes data) + function hashZeroExTransaction(uint256 salt, address signerAddress, bytes data) internal pure returns (bytes32) @@ -59,19 +59,19 @@ contract MixinTransactions is return keccak256(abi.encode( EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, salt, - signer, + signerAddress, keccak256(data) )); } /// @dev Executes an exchange method call in the context of signer. /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signer Address of transaction signer. + /// @param signerAddress Address of transaction signer. /// @param data AbiV2 encoded calldata. /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, - address signer, + address signerAddress, bytes data, bytes signature ) @@ -83,7 +83,11 @@ contract MixinTransactions is REENTRANCY_ILLEGAL ); - bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(salt, signer, data)); + bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction( + salt, + signerAddress, + data + )); // Validate transaction has not been executed require( @@ -92,15 +96,19 @@ contract MixinTransactions is ); // Transaction always valid if signer is sender of transaction - if (signer != msg.sender) { + if (signerAddress != msg.sender) { // Validate signature require( - isValidSignature(transactionHash, signer, signature), + isValidSignature( + transactionHash, + signerAddress, + signature + ), INVALID_TX_SIGNATURE ); // Set the current transaction signer - currentContextAddress = signer; + currentContextAddress = signerAddress; } // Execute transaction diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol index 02aa9776e..511463309 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol @@ -22,32 +22,32 @@ contract ISignatureValidator { /// @dev Approves a hash on-chain using any valid signature type. /// After presigning a hash, the preSign signature type will become valid for that hash and signer. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof that the hash has been signed by signer. function preSign( bytes32 hash, - address signer, + address signerAddress, bytes signature ) external; /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. - /// @param validator Address of Validator contract. + /// @param validatorAddress Address of Validator contract. /// @param approval Approval or disapproval of Validator contract. function setSignatureValidatorApproval( - address validator, + address validatorAddress, bool approval ) external; /// @dev Verifies that a signature is valid. /// @param hash Message hash that is signed. - /// @param signer Address of signer. + /// @param signerAddress Address of signer. /// @param signature Proof of signing. /// @return Validity of order signature. function isValidSignature( bytes32 hash, - address signer, + address signerAddress, bytes memory signature ) public diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol index 2f9a5bc7c..a7cab8f55 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol @@ -21,12 +21,12 @@ contract ITransactions { /// @dev Executes an exchange method call in the context of signer. /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signer Address of transaction signer. + /// @param signerAddress Address of transaction signer. /// @param data AbiV2 encoded calldata. /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, - address signer, + address signerAddress, bytes data, bytes signature ) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IValidator.sol index 3e5ccc190..0b1796a66 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IValidator.sol @@ -22,12 +22,12 @@ contract IValidator { /// @dev Verifies that a signature is valid. /// @param hash Message hash that is signed. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof of signing. /// @return Validity of order signature. function isValidSignature( bytes32 hash, - address signer, + address signerAddress, bytes signature ) external diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol index b7550d6d2..63f1b8c87 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol @@ -25,16 +25,16 @@ contract LibFillResults is { struct FillResults { - uint256 makerAssetFilledAmount; - uint256 takerAssetFilledAmount; - uint256 makerFeePaid; - uint256 takerFeePaid; + uint256 makerAssetFilledAmount; // Total amount of makerAsset(s) filled. + uint256 takerAssetFilledAmount; // Total amount of takerAsset(s) filled. + uint256 makerFeePaid; // Total amount of ZRX paid by maker(s) to feeRecipient(s). + uint256 takerFeePaid; // Total amount of ZRX paid by taker to feeRecipients(s). } struct MatchedFillResults { - FillResults left; - FillResults right; - uint256 leftMakerAssetSpreadAmount; + FillResults left; // Amounts filled and fees paid of left order. + FillResults right; // Amounts filled and fees paid of right order. + uint256 leftMakerAssetSpreadAmount; // Spread between price of left and right order, denominated in the left order's makerAsset, paid to taker. } /// @dev Adds properties of both FillResults instances. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index bfc7aaae0..f3f1e9277 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol @@ -55,27 +55,24 @@ contract LibOrder is } struct Order { - address makerAddress; - address takerAddress; - address feeRecipientAddress; - address senderAddress; - uint256 makerAssetAmount; - uint256 takerAssetAmount; - uint256 makerFee; - uint256 takerFee; - uint256 expirationTimeSeconds; - uint256 salt; - bytes makerAssetData; - bytes takerAssetData; + address makerAddress; // Address that created the order. + address takerAddress; // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + address feeRecipientAddress; // Address that will recieve fees when order is filled. + address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0. + uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0. + uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted. + uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted. + uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires. + uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash. + bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The last byte references the id of this proxy. + bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The last byte references the id of this proxy. } struct OrderInfo { - // See LibStatus for a complete description of order statuses - uint8 orderStatus; - // Keccak-256 EIP712 hash of the order - bytes32 orderHash; - // Amount of order that has been filled - uint256 orderTakerAssetFilledAmount; + uint8 orderStatus; // Status that describes order's validity and fillability. + bytes32 orderHash; // EIP712 hash of the order (see LibOrder.getOrderHash). + uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled. } /// @dev Calculates Keccak-256 hash of the order. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol index d16a830f4..788f42c60 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -27,9 +27,9 @@ contract MAssetProxyDispatcher is // Logs registration of new asset proxy event AssetProxySet( - uint8 id, - address newAssetProxy, - address oldAssetProxy + uint8 id, // Id of new registered AssetProxy. + address newAssetProxy, // Address of new registered AssetProxy. + address oldAssetProxy // Address of AssetProxy that was overwritten at given id (or null address). ); /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol index 1737f5baf..6e406e1c4 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -28,32 +28,34 @@ contract MExchangeCore is { // Fill event is emitted whenever an order is filled. event Fill( - address indexed makerAddress, - address takerAddress, - address indexed feeRecipientAddress, - uint256 makerAssetFilledAmount, - uint256 takerAssetFilledAmount, - uint256 makerFeePaid, - uint256 takerFeePaid, - bytes32 indexed orderHash, - bytes makerAssetData, - bytes takerAssetData + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that received fees. + address takerAddress, // Address that filled the order. + address senderAddress, // Address that called the Exchange contract (msg.sender). + uint256 makerAssetFilledAmount, // Amount of makerAsset sold by maker and bought by taker. + uint256 takerAssetFilledAmount, // Amount of takerAsset sold by taker and bought by maker. + uint256 makerFeePaid, // Amount of ZRX paid to feeRecipient by maker. + uint256 takerFeePaid, // Amount of ZRX paid to feeRecipient by taker. + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. ); // Cancel event is emitted whenever an individual order is cancelled. event Cancel( - address indexed makerAddress, - address indexed feeRecipientAddress, - bytes32 indexed orderHash, - bytes makerAssetData, - bytes takerAssetData + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that would have recieved fees if order was filled. + address senderAddress, // Address that called the Exchange contract (msg.sender). + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. ); // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully. event CancelUpTo( - address indexed makerAddress, - address indexed senderAddress, - uint256 orderEpoch + address indexed makerAddress, // Orders cancelled must have been created by this address. + address indexed senderAddress, // Orders cancelled must have a `senderAddress` equal to this address. + uint256 orderEpoch // Orders specified makerAddress and senderAddress with a salt <= this value are considered cancelled. ); /// @dev Updates state with results of a fill order. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol index 9c6fbe22b..6cc1d7a10 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol @@ -23,17 +23,23 @@ import "../interfaces/ISignatureValidator.sol"; contract MSignatureValidator is ISignatureValidator { + event SignatureValidatorApproval( + address indexed signerAddress, // Address that approves or disapproves a contract to verify signatures. + address indexed validatorAddress, // Address of signature validator contract. + bool approved // Approval or disapproval of validator contract. + ); + // Allowed signature types. enum SignatureType { - Illegal, // 0x00, default value - Invalid, // 0x01 - EIP712, // 0x02 - EthSign, // 0x03 - Caller, // 0x04 - Wallet, // 0x05 - Validator, // 0x06 - PreSigned, // 0x07 - Trezor, // 0x08 - NSignatureTypes // 0x09, number of signature types. Always leave at end. + Illegal, // 0x00, default value + Invalid, // 0x01 + EIP712, // 0x02 + EthSign, // 0x03 + Caller, // 0x04 + Wallet, // 0x05 + Validator, // 0x06 + PreSigned, // 0x07 + Trezor, // 0x08 + NSignatureTypes // 0x09, number of signature types. Always leave at end. } } diff --git a/packages/contracts/src/contracts/current/test/TestValidator/TestValidator.sol b/packages/contracts/src/contracts/current/test/TestValidator/TestValidator.sol index 13953b482..f9271bf7a 100644 --- a/packages/contracts/src/contracts/current/test/TestValidator/TestValidator.sol +++ b/packages/contracts/src/contracts/current/test/TestValidator/TestValidator.sol @@ -35,18 +35,18 @@ contract TestValidator is /// @dev Verifies that a signature is valid. `signer` must match `VALID_SIGNER`. /// @param hash Message hash that is signed. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof of signing. /// @return Validity of signature. function isValidSignature( bytes32 hash, - address signer, + address signerAddress, bytes signature ) external view returns (bool isValid) { - return (signer == VALID_SIGNER); + return (signerAddress == VALID_SIGNER); } } diff --git a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol index 460c9ea42..d35815474 100644 --- a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol +++ b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol @@ -116,18 +116,18 @@ contract Whitelist is /// @dev Verifies signer is same as signer of current Ethereum transaction. /// NOTE: This function can currently be used to validate signatures coming from outside of this contract. /// Extra safety checks can be added for a production contract. - /// @param signer Address that should have signed the given hash. + /// @param signerAddress Address that should have signed the given hash. /// @param signature Proof of signing. /// @return Validity of order signature. function isValidSignature( bytes32 hash, - address signer, + address signerAddress, bytes signature ) external view returns (bool isValid) { - return signer == tx.origin; + return signerAddress == tx.origin; } } diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 4cc8f0b89..06eb70644 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -215,7 +215,7 @@ export class ExchangeWrapper { ): Promise<TransactionReceiptWithDecodedLogs> { const txHash = await this._exchange.executeTransaction.sendTransactionAsync( signedTx.salt, - signedTx.signer, + signedTx.signerAddress, signedTx.data, signedTx.signature, { from }, diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts index 19ef4e1bf..348c0715d 100644 --- a/packages/contracts/src/utils/transaction_factory.ts +++ b/packages/contracts/src/utils/transaction_factory.ts @@ -9,7 +9,7 @@ const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = { name: 'ZeroExTransaction', parameters: [ { name: 'salt', type: EIP712Types.Uint256 }, - { name: 'signer', type: EIP712Types.Address }, + { name: 'signerAddress', type: EIP712Types.Address }, { name: 'data', type: EIP712Types.Bytes }, ], }; @@ -25,10 +25,10 @@ export class TransactionFactory { } public newSignedTransaction(data: string, signatureType: SignatureType = SignatureType.EthSign): SignedTransaction { const salt = generatePseudoRandomSalt(); - const signer = `0x${this._signerBuff.toString('hex')}`; + const signerAddress = `0x${this._signerBuff.toString('hex')}`; const executeTransactionData = { salt, - signer, + signerAddress, data, }; const executeTransactionHashBuff = EIP712Utils.structHash( diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index a6d0c2a88..7a1f92afd 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -108,7 +108,7 @@ export enum ContractName { export interface SignedTransaction { exchangeAddress: string; salt: BigNumber; - signer: string; + signerAddress: string; data: string; signature: string; } diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index ea37a1e99..89a1c6492 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -399,6 +399,7 @@ describe('Exchange core', () => { expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); expect(takerAddress).to.be.equal(logArgs.takerAddress); + expect(takerAddress).to.be.equal(logArgs.senderAddress); expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); @@ -577,6 +578,7 @@ describe('Exchange core', () => { const logArgs = log.args; expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); + expect(signedOrder.makerAddress).to.be.equal(logArgs.senderAddress); expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index 2a94ab25a..8e221e3f1 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -2,9 +2,13 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { addSignedMessagePrefix, assetProxyUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils'; import { SignatureType, SignedOrder } from '@0xproject/types'; import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); -import { TestSignatureValidatorContract } from '../../src/generated_contract_wrappers/test_signature_validator'; +import { + SignatureValidatorApprovalContractEventArgs, + TestSignatureValidatorContract, +} from '../../src/generated_contract_wrappers/test_signature_validator'; import { TestValidatorContract } from '../../src/generated_contract_wrappers/test_validator'; import { TestWalletContract } from '../../src/generated_contract_wrappers/test_wallet'; import { addressUtils } from '../../src/utils/address_utils'; @@ -12,6 +16,7 @@ import { artifacts } from '../../src/utils/artifacts'; import { expectRevertOrOtherErrorAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; +import { LogDecoder } from '../../src/utils/log_decoder'; import { OrderFactory } from '../../src/utils/order_factory'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; @@ -19,7 +24,7 @@ chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - +// tslint:disable:no-unnecessary-type-assertion describe('MixinSignatureValidator', () => { let signedOrder: SignedOrder; let orderFactory: OrderFactory; @@ -30,6 +35,7 @@ describe('MixinSignatureValidator', () => { let signerPrivateKey: Buffer; let notSignerAddress: string; let notSignerPrivateKey: Buffer; + let signatureValidatorLogDecoder: LogDecoder; before(async () => { await blockchainLifecycle.startAsync(); @@ -59,6 +65,7 @@ describe('MixinSignatureValidator', () => { txDefaults, signerAddress, ); + signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, signatureValidator.address); await web3Wrapper.awaitTransactionSuccessAsync( await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { from: signerAddress, @@ -456,4 +463,45 @@ describe('MixinSignatureValidator', () => { expect(isValidSignature).to.be.false(); }); }); + + describe('setSignatureValidatorApproval', () => { + it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { + const approval = true; + const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + testValidator.address, + approval, + { + from: signerAddress, + }, + ), + ); + expect(res.logs.length).to.equal(1); + const log = res.logs[0] as LogWithDecodedArgs<SignatureValidatorApprovalContractEventArgs>; + const logArgs = log.args; + expect(logArgs.signerAddress).to.equal(signerAddress); + expect(logArgs.validatorAddress).to.equal(testValidator.address); + expect(logArgs.approved).to.equal(approval); + }); + it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { + const approval = false; + const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + testValidator.address, + approval, + { + from: signerAddress, + }, + ), + ); + expect(res.logs.length).to.equal(1); + const log = res.logs[0] as LogWithDecodedArgs<SignatureValidatorApprovalContractEventArgs>; + const logArgs = log.args; + expect(logArgs.signerAddress).to.equal(signerAddress); + expect(logArgs.validatorAddress).to.equal(testValidator.address); + expect(logArgs.approved).to.equal(approval); + }); + }); }); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index fb2215eaa..fed305bd3 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -75,6 +75,7 @@ export class RevertTraceSubprovider extends TraceCollectionSubprovider { } const bytecodeHex = stripHexPrefix(bytecode); const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; + const pcToSourceRange = parseSourceMap( contractData.sourceCodes, sourceMap, @@ -88,16 +89,19 @@ export class RevertTraceSubprovider extends TraceCollectionSubprovider { // actually happens in assembly). In that case, we want to keep // searching backwards by decrementing the pc until we find a // mapped source range. - while (_.isUndefined(sourceRange)) { + while (_.isUndefined(sourceRange) && pc > 0) { sourceRange = pcToSourceRange[pc]; pc -= 1; - if (pc <= 0) { - this._logger.warn( - `could not find matching sourceRange for structLog: ${evmCallStackEntry.structLog}`, - ); - continue; - } } + if (_.isUndefined(sourceRange)) { + this._logger.warn( + `could not find matching sourceRange for structLog: ${JSON.stringify( + _.omit(evmCallStackEntry.structLog, 'stack'), + )}`, + ); + continue; + } + const fileIndex = contractData.sources.indexOf(sourceRange.fileName); const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]); if (sourceSnippet !== null) { diff --git a/packages/sol-cov/src/utils.ts b/packages/sol-cov/src/utils.ts index d31696636..b696bd463 100644 --- a/packages/sol-cov/src/utils.ts +++ b/packages/sol-cov/src/utils.ts @@ -5,6 +5,10 @@ import * as _ from 'lodash'; import { ContractData, LineColumn, SingleFileSourceRange } from './types'; +// This is the minimum length of valid contract bytecode. The Solidity compiler +// metadata is 86 bytes. If you add the '0x' prefix, we get 88. +const MIN_CONTRACT_BYTECODE_LENGTH = 88; + export const utils = { compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; @@ -38,7 +42,15 @@ export const utils = { } const contractData = _.find(contractsData, contractDataCandidate => { const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); + // If the bytecode is less than the minimum length, we are probably + // dealing with an interface. This isn't what we're looking for. + if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); + if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so // collisions are practically impossible and it allows us to reuse that code return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); diff --git a/packages/website/public/images/team/chris.png b/packages/website/public/images/team/chris.png Binary files differnew file mode 100644 index 000000000..242a2813f --- /dev/null +++ b/packages/website/public/images/team/chris.png diff --git a/packages/website/public/images/team/mel.png b/packages/website/public/images/team/mel.png Binary files differnew file mode 100644 index 000000000..52d779ad2 --- /dev/null +++ b/packages/website/public/images/team/mel.png diff --git a/packages/website/ts/components/onboarding/onboarding_card.tsx b/packages/website/ts/components/onboarding/onboarding_card.tsx new file mode 100644 index 000000000..bc83b8034 --- /dev/null +++ b/packages/website/ts/components/onboarding/onboarding_card.tsx @@ -0,0 +1,84 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; +import { IconButton } from 'ts/components/ui/icon_button'; +import { Island } from 'ts/components/ui/island'; +import { Text, Title } from 'ts/components/ui/text'; + +export type ContinueButtonDisplay = 'enabled' | 'disabled'; + +export interface OnboardingCardProps { + title?: string; + content: React.ReactNode; + isLastStep: boolean; + onClose: () => void; + onClickNext: () => void; + onClickBack: () => void; + continueButtonDisplay?: ContinueButtonDisplay; + shouldHideBackButton?: boolean; + shouldHideNextButton?: boolean; + continueButtonText?: string; + borderRadius?: string; +} + +export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({ + title, + content, + continueButtonDisplay, + continueButtonText, + onClickNext, + onClickBack, + onClose, + shouldHideBackButton, + shouldHideNextButton, + borderRadius, +}) => ( + <Island borderRadius={borderRadius}> + <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px"> + <div className="flex flex-column"> + <div className="flex justify-between"> + <Title>{title}</Title> + <Container position="relative" bottom="20px" left="15px"> + <IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}> + Close + </IconButton> + </Container> + </div> + <Container marginBottom="15px"> + <Text>{content}</Text> + </Container> + {continueButtonDisplay && ( + <Button + isDisabled={continueButtonDisplay === 'disabled'} + onClick={onClickNext} + fontColor={colors.white} + fontSize="15px" + backgroundColor={colors.mediumBlue} + > + {continueButtonText} + </Button> + )} + <Container className="flex justify-between" marginTop="15px"> + {!shouldHideBackButton && ( + <Text fontColor={colors.grey} onClick={onClickBack}> + Back + </Text> + )} + {!shouldHideNextButton && ( + <Text fontColor={colors.grey} onClick={onClickNext}> + Skip + </Text> + )} + </Container> + </div> + </Container> + </Island> +); + +OnboardingCard.defaultProps = { + continueButtonText: 'Continue', +}; + +OnboardingCard.displayName = 'OnboardingCard'; diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 34aeace82..331899469 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import { Placement, Popper, PopperChildrenProps } from 'react-popper'; +import { OnboardingCard } from 'ts/components/onboarding/onboarding_card'; import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip'; +import { Animation } from 'ts/components/ui/animation'; import { Container } from 'ts/components/ui/container'; import { Overlay } from 'ts/components/ui/overlay'; @@ -22,26 +24,37 @@ export interface OnboardingFlowProps { isRunning: boolean; onClose: () => void; updateOnboardingStep: (stepIndex: number) => void; + disableOverlay?: boolean; + isMobile: boolean; } export class OnboardingFlow extends React.Component<OnboardingFlowProps> { + public static defaultProps = { + disableOverlay: false, + isMobile: false, + }; public render(): React.ReactNode { if (!this.props.isRunning) { return null; } - return ( - <Overlay> + let onboardingElement = null; + if (this.props.isMobile) { + onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>; + } else { + onboardingElement = ( <Popper referenceElement={this._getElementForStep()} placement={this._getCurrentStep().placement}> {this._renderPopperChildren.bind(this)} </Popper> - </Overlay> - ); + ); + } + if (this.props.disableOverlay) { + return onboardingElement; + } + return <Overlay>{onboardingElement}</Overlay>; } - private _getElementForStep(): Element { return document.querySelector(this._getCurrentStep().target); } - private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode { return ( <div ref={props.ref} style={props.style} data-placement={props.placement}> @@ -49,13 +62,12 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { </div> ); } - private _renderToolTip(): React.ReactNode { const { steps, stepIndex } = this.props; const step = steps[stepIndex]; const isLastStep = steps.length - 1 === stepIndex; return ( - <Container marginLeft="30px"> + <Container marginLeft="30px" maxWidth={350}> <OnboardingTooltip title={step.title} content={step.content} @@ -72,10 +84,31 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { ); } + private _renderOnboardignCard(): React.ReactNode { + const { steps, stepIndex } = this.props; + const step = steps[stepIndex]; + const isLastStep = steps.length - 1 === stepIndex; + return ( + <Container position="relative" zIndex={1} maxWidth="100vw"> + <OnboardingCard + title={step.title} + content={step.content} + isLastStep={isLastStep} + shouldHideBackButton={step.shouldHideBackButton} + shouldHideNextButton={step.shouldHideNextButton} + onClose={this.props.onClose} + onClickNext={this._goToNextStep.bind(this)} + onClickBack={this._goToPrevStep.bind(this)} + continueButtonDisplay={step.continueButtonDisplay} + continueButtonText={step.continueButtonText} + borderRadius="10px 10px 0px 0px" + /> + </Container> + ); + } private _getCurrentStep(): Step { return this.props.steps[this.props.stepIndex]; } - private _goToNextStep(): void { const nextStep = this.props.stepIndex + 1; if (nextStep < this.props.steps.length) { @@ -84,7 +117,6 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { this.props.onClose(); } } - private _goToPrevStep(): void { const nextStep = this.props.stepIndex - 1; if (nextStep >= 0) { diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index 45851b4de..d8065625d 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -1,90 +1,25 @@ -import { colors } from '@0xproject/react-shared'; import * as React from 'react'; -import { Button } from 'ts/components/ui/button'; -import { Container } from 'ts/components/ui/container'; -import { IconButton } from 'ts/components/ui/icon_button'; -import { Island } from 'ts/components/ui/island'; +import { OnboardingCard, OnboardingCardProps } from 'ts/components/onboarding/onboarding_card'; import { Pointer, PointerDirection } from 'ts/components/ui/pointer'; -import { Text, Title } from 'ts/components/ui/text'; export type ContinueButtonDisplay = 'enabled' | 'disabled'; -export interface OnboardingTooltipProps { - title?: string; - content: React.ReactNode; - isLastStep: boolean; - onClose: () => void; - onClickNext: () => void; - onClickBack: () => void; - continueButtonDisplay?: ContinueButtonDisplay; - shouldHideBackButton?: boolean; - shouldHideNextButton?: boolean; - pointerDirection?: PointerDirection; - continueButtonText?: string; +export interface OnboardingTooltipProps extends OnboardingCardProps { className?: string; + pointerDirection?: PointerDirection; } -export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = ({ - title, - content, - continueButtonDisplay, - continueButtonText, - onClickNext, - onClickBack, - onClose, - shouldHideBackButton, - shouldHideNextButton, - pointerDirection, - className, -}) => ( - <Pointer className={className} direction={pointerDirection}> - <Island> - <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px"> - <div className="flex flex-column"> - <div className="flex justify-between"> - <Title>{title}</Title> - <Container position="relative" bottom="20px" left="15px"> - <IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}> - Close - </IconButton> - </Container> - </div> - <Container marginBottom="15px"> - <Text>{content}</Text> - </Container> - {continueButtonDisplay && ( - <Button - isDisabled={continueButtonDisplay === 'disabled'} - onClick={onClickNext} - fontColor={colors.white} - fontSize="15px" - backgroundColor={colors.mediumBlue} - > - {continueButtonText} - </Button> - )} - <Container className="flex justify-between" marginTop="15px"> - {!shouldHideBackButton && ( - <Text fontColor={colors.grey} onClick={onClickBack}> - Back - </Text> - )} - {!shouldHideNextButton && ( - <Text fontColor={colors.grey} onClick={onClickNext}> - Skip - </Text> - )} - </Container> - </div> - </Container> - </Island> - </Pointer> -); - +export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = props => { + const { pointerDirection, className, ...cardProps } = props; + return ( + <Pointer className={className} direction={pointerDirection}> + <OnboardingCard {...cardProps} /> + </Pointer> + ); +}; OnboardingTooltip.defaultProps = { pointerDirection: 'left', - continueButtonText: 'Continue', }; OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 7e40192f6..10d4af30e 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -14,7 +14,7 @@ import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowa import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step'; import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; -import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { utils } from 'ts/utils/utils'; @@ -34,6 +34,7 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> { updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; + screenWidth: ScreenWidths; } class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { @@ -57,6 +58,8 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp isRunning={this.props.isRunning} onClose={this._closeOnboarding.bind(this)} updateOnboardingStep={this._updateOnboardingStep.bind(this)} + disableOverlay={this.props.screenWidth === ScreenWidths.Sm} + isMobile={this.props.screenWidth === ScreenWidths.Sm} /> ); } diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx index 6e6a74a06..0039aa545 100644 --- a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx @@ -10,7 +10,7 @@ export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOn <Container marginTop="15px" marginBottom="15px"> <img src="/images/metamask_icon.png" height="50px" width="50px" /> </Container> - <Text>Unlock your metamask extension to begin.</Text> + <Text center={true}>Unlock your metamask extension to get started.</Text> </div> </div> ); diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 67314678b..11b3b43f4 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -246,11 +246,6 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <div style={styles.root}> - <PortalOnboardingFlow - blockchain={this._blockchain} - trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} - refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} - /> <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} @@ -307,6 +302,11 @@ export class Portal extends React.Component<PortalProps, PortalState> { tokenVisibility={tokenVisibility} /> </div> + <PortalOnboardingFlow + blockchain={this._blockchain} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} + /> </div> ); } @@ -340,62 +340,77 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderWallet(): React.ReactNode { + const startOnboarding = this._renderStartOnboarding(); + const isMobile = this.props.screenWidth === ScreenWidths.Sm; + // We need room to scroll down for mobile onboarding + const marginBottom = isMobile ? '200px' : '15px'; return ( <div> - <Wallet - style={this.props.isPortalOnboardingShowing ? { zIndex: zIndex.aboveOverlay } : 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 marginTop="15px"> - <Island> - <Container - marginTop="30px" - marginBottom="30px" - marginLeft="30px" - marginRight="30px" - className="flex justify-around items-center" - > - <ActionAccountBalanceWallet - style={{ width: '30px', height: '30px' }} - color={colors.orange} - /> - <Text - fontColor={colors.grey} - fontSize="16px" - center={true} - onClick={this._startOnboarding.bind(this)} - > - Learn how to set up your account - </Text> - </Container> - </Island> + {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>} </div> ); } + private _renderStartOnboarding(): React.ReactNode { + return ( + <Island> + <Container + marginTop="30px" + marginBottom="30px" + marginLeft="30px" + marginRight="30px" + className="flex justify-around items-center" + > + <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} /> + <Text + fontColor={colors.grey} + fontSize="16px" + center={true} + onClick={this._startOnboarding.bind(this)} + > + Learn how to set up your account + </Text> + </Container> + </Island> + ); + } private _startOnboarding(): void { const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep); this.props.dispatcher.updatePortalOnboardingShowing(true); + // On mobile, make sure the wallet is completely visible. + if (this.props.screenWidth === ScreenWidths.Sm) { + document.querySelector('.wallet').scrollIntoView(); + } } private _renderWalletSection(): React.ReactNode { return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />; diff --git a/packages/website/ts/components/ui/animation.tsx b/packages/website/ts/components/ui/animation.tsx new file mode 100644 index 000000000..cbda2993d --- /dev/null +++ b/packages/website/ts/components/ui/animation.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { keyframes, styled } from 'ts/style/theme'; + +export type AnimationType = 'easeUpFromBottom'; + +export interface AnimationProps { + type: AnimationType; +} + +const PlainAnimation: React.StatelessComponent<AnimationProps> = props => <div {...props} />; + +const appearFromBottomFrames = keyframes` + from { + position: absolute; + bottom: -500px; + } + + to { + position: absolute; + bottom: 0px; + } +`; + +const animations: { [K in AnimationType]: string } = { + easeUpFromBottom: `${appearFromBottomFrames} 1s ease 0s 1 forwards`, +}; + +export const Animation = styled(PlainAnimation)` + animation: ${props => animations[props.type]}; +`; + +Animation.displayName = 'Animation'; diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx index cb542a386..02fa47480 100644 --- a/packages/website/ts/components/ui/button.tsx +++ b/packages/website/ts/components/ui/button.tsx @@ -1,5 +1,5 @@ import { colors } from '@0xproject/react-shared'; -import { darken, grayscale } from 'polished'; +import { darken, saturate } from 'polished'; import * as React from 'react'; import { styled } from 'ts/style/theme'; @@ -17,7 +17,7 @@ export interface ButtonProps { } const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => ( - <button type={type} className={className} onClick={isDisabled ? undefined : onClick}> + <button type={type} className={className} onClick={isDisabled ? undefined : onClick} disabled={isDisabled}> {children} </button> ); @@ -26,14 +26,15 @@ export const Button = styled(PlainButton)` cursor: ${props => (props.isDisabled ? 'default' : 'pointer')}; font-size: ${props => props.fontSize}; color: ${props => props.fontColor}; - transition: background-color 0.5s ease; + transition: background-color, opacity 0.5s ease; padding: 0.8em 2.2em; border-radius: 6px; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; + outline: none; font-family: ${props => props.fontFamily}; width: ${props => props.width}; - background-color: ${props => (props.isDisabled ? grayscale(props.backgroundColor) : props.backgroundColor)}; + background-color: ${props => props.backgroundColor}; border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; &:hover { background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')}; @@ -41,6 +42,13 @@ export const Button = styled(PlainButton)` &:active { background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')}; } + &:disabled { + opacity: 0.5; + box-shadow: none; + } + &:focus { + background-color: ${props => saturate(0.2, props.backgroundColor)}; + } `; Button.defaultProps = { diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index 1776345da..90aec0e7c 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -23,6 +23,7 @@ export interface ContainerProps { left?: string; right?: string; bottom?: string; + zIndex?: number; } export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => { diff --git a/packages/website/ts/components/ui/island.tsx b/packages/website/ts/components/ui/island.tsx index fbb9989a4..c8abfb7e0 100644 --- a/packages/website/ts/components/ui/island.tsx +++ b/packages/website/ts/components/ui/island.tsx @@ -1,31 +1,28 @@ import * as React from 'react'; import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; export interface IslandProps { style?: React.CSSProperties; - children?: React.ReactNode; className?: string; Component?: string | React.ComponentClass<any> | React.StatelessComponent<any>; + borderRadius?: string; } -const defaultStyle: React.CSSProperties = { - backgroundColor: colors.white, - borderBottomRightRadius: 10, - borderBottomLeftRadius: 10, - borderTopRightRadius: 10, - borderTopLeftRadius: 10, - boxShadow: `0px 3px 4px ${colors.walletBoxShadow}`, - overflow: 'hidden', -}; - -export const Island: React.StatelessComponent<IslandProps> = (props: IslandProps) => ( - <props.Component style={{ ...defaultStyle, ...props.style }} className={props.className}> - {props.children} - </props.Component> +const PlainIsland: React.StatelessComponent<IslandProps> = ({ Component, style, className, children }) => ( + <Component style={style} className={className} children={children} /> ); +export const Island = styled(PlainIsland)` + background-color: ${colors.white}; + border-radius: ${props => props.borderRadius}; + box-shadow: 0px 4px 6px ${colors.walletBoxShadow}; + overflow: hidden; +`; + Island.defaultProps = { Component: 'div', + borderRadius: '10px', style: {}, }; diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 82fec4e48..f88fd6c24 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -86,7 +86,6 @@ interface AccessoryItemConfig { const styles: Styles = { root: { width: '100%', - position: 'relative', }, footerItemInnerDiv: { paddingLeft: 24, diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts index ba2b8f512..12daad021 100644 --- a/packages/website/ts/containers/portal_onboarding_flow.ts +++ b/packages/website/ts/containers/portal_onboarding_flow.ts @@ -3,7 +3,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { Blockchain } from 'ts/blockchain'; -import { ActionTypes, ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { ActionTypes, ProviderType, ScreenWidths, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow'; import { State } from 'ts/redux/reducer'; @@ -25,6 +25,7 @@ interface ConnectedState { blockchainIsLoaded: boolean; userEtherBalanceInWei?: BigNumber; tokenByAddress: TokenByAddress; + screenWidth: ScreenWidths; } interface ConnectedDispatch { @@ -43,6 +44,7 @@ const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): Co userEtherBalanceInWei: state.userEtherBalanceInWei, tokenByAddress: state.tokenByAddress, hasBeenSeen: state.hasPortalOnboardingBeenSeen, + screenWidth: state.screenWidth, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 3136dbca3..6830b64ab 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -150,12 +150,30 @@ const teamRow5: ProfileInfo[] = [ }, { name: 'Francesco Agosti', - title: 'Senior Frontend Engineer', + title: 'Engineer', description: `Full-stack engineer. Previously senior software engineer at Yelp. Computer Science at Duke.`, image: 'images/team/fragosti.png', 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', + 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 advisors: ProfileInfo[] = [ @@ -252,6 +270,7 @@ export class About extends React.Component<AboutProps, AboutState> { <div className="clearfix">{this._renderProfiles(teamRow3)}</div> <div className="clearfix">{this._renderProfiles(teamRow4)}</div> <div className="clearfix">{this._renderProfiles(teamRow5)}</div> + <div className="clearfix">{this._renderProfiles(teamRow6)}</div> </div> <div className="pt3 pb2"> <div diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index dd046a8cb..e73b1f193 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -47,7 +47,7 @@ export const Profile = (props: ProfileProps) => { <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> {props.profileInfo.description} </div> - <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> + <div className="flex pb3 sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> {renderSocialMediaIcons(props.profileInfo)} </div> </div> |