diff --git a/contracts/asset-proxy/contracts/src/bridges/DyDxBridge.sol b/contracts/asset-proxy/contracts/src/bridges/DyDxBridge.sol new file mode 100644 index 0000000000..9f21608b26 --- /dev/null +++ b/contracts/asset-proxy/contracts/src/bridges/DyDxBridge.sol @@ -0,0 +1,293 @@ +/* + + Copyright 2019 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.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; +import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "@0x/contracts-utils/contracts/src/Authorizable.sol"; +import "../interfaces/IERC20Bridge.sol"; +import "../interfaces/IDydx.sol"; +import "../interfaces/IAssetData.sol"; + +// solhint-disable space-after-comma +contract DydxBridge is + IERC20Bridge, + DeploymentConstants, + Authorizable +{ + + using LibBytes for bytes; + + // Return values for `isValidSignature` + // bytes4(keccak256('isValidSignature(bytes,bytes)')) + bytes4 constant VALID_SIGNATURE_RETURN_VALUE = bytes4(0x20c13b0b); + bytes4 constant INVALID_SIGNATURE_RETURN_VALUE = bytes4(0); + + // OrderWithHash(LibOrder.Order order, bytes32 orderHash).selector + // == bytes4(keccak256('OrderWithHash((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),bytes32)')) + bytes4 constant EIP1271_ORDER_WITH_HASH_SELECTOR = bytes4(0x3efe50c8); + + struct BridgeData { + // Fields used by dydx + address dydxAccountOwner; // The owner of the dydx account. + uint256 dydxAccountNumber; // Account number used to identify the owner's specific account. + address dydxAccountOperator; // Operator of dydx account who signed the order (if transferred by an operator). + uint256 dydxFromMarketId; // Market ID of `from` asset. + uint256 dydxToMarketId; // Market ID of `to` asset. + // Fields used by bridge + bool shouldDepositIntodydx; // True iff contract balance should be deposited into dydx account. + address fromTokenAddress; // The token given to `from` or deposited into the dydx account. + } + + /// @dev Callback for `IERC20Bridge`. + /// Function Prerequisite: + /// 1. Tokens are held in this contract that correspond to `dydxFromMarketId`, and + /// 2. Tokens are held in a dydx account that correspond to `dydxToMarketId` + /// + /// When called, two actions take place: + /// 1. The total balance held by this contract is either (i) deposited into the dydx account OR (ii) transferred to `from`. + /// This is dictated by `BridgeData.shouldDepositIntoDydx`. + /// 2. Some `amount` of tokens are withdrawn from the dydx account into the address `to`. + /// + /// Notes: + /// 1. This bridge must be set as an operator of the dydx account that is being operated on. + /// 2. This function may only be called in the context of the 0x Exchange executing an order (ERC20Bridge is authorized). + /// 3. The order must be signed by the owner or an operator of the dydx account. This is validated in `isValidSignature`. + /// @param from The sender of the tokens. + /// @param to The recipient of the tokens. + /// @param amount Minimum amount of `toTokenAddress` tokens to buy. + /// @param encodedBridgeData An abi-encoded `BridgeData` struct. + /// @return success The magic bytes if successful. + function bridgeTransferFrom( + address, + address from, + address to, + uint256 amount, + bytes calldata encodedBridgeData + ) + external + onlyAuthorized + returns (bytes4 success) + { + // Decode bridge data. + (BridgeData memory bridgeData) = abi.decode(encodedBridgeData, (BridgeData)); + + // Cache dydx contract. + IDydx dydx = IDydx(_getDydxAddress()); + + // Cache the balance held by this contract. + IERC20Token fromToken = IERC20Token(bridgeData.fromTokenAddress); + uint256 fromTokenAmount = fromToken.balanceOf(address(this)); + uint256 toTokenAmount = amount; + + // Construct dydx account info. + IDydx.AccountInfo[] memory accounts = new IDydx.AccountInfo[](1); + accounts[0] = IDydx.AccountInfo({ + owner: bridgeData.dydxAccountOwner, + number: bridgeData.dydxAccountNumber + }); + + // Construct arguments to `dydx.operate`. + IDydx.ActionArgs[] memory actions; + if (bridgeData.shouldDepositIntodydx) { + // Generate deposit/withdraw actions + actions = new IDydx.ActionArgs[](2); + actions[0] = _createDepositAction( + address(this), // deposit `fromToken` into dydx from this contract. + fromTokenAmount, // amount to deposit. + bridgeData // bridge data. + ); + actions[1] = _createWithdrawAction( + to, // withdraw `toToken` from dydx to `to`. + toTokenAmount, // amount to withdraw. + bridgeData // bridge data. + ); + + // Allow dydx to deposit `fromToken` from this contract. + LibERC20Token.approve( + bridgeData.fromTokenAddress, + address(dydx), + uint256(-1) + ); + } else { + // Generate withdraw action + actions = new IDydx.ActionArgs[](1); + actions[0] = _createWithdrawAction(to, toTokenAmount, bridgeData); + + // Transfer `fromToken` to `from` + require( + fromToken.transfer(from, fromTokenAmount), + "TRANSFER_OF_FROM_TOKEN_FAILED" + ); + } + + // Run operations. This will revert on failure. + dydx.operate(accounts, actions); + return BRIDGE_SUCCESS; + } + + /// @dev Returns a dydx `DepositAction`. + function _createDepositAction( + address depositFrom, + uint256 amount, + BridgeData memory bridgeData + ) + internal + pure + returns (IDydx.ActionArgs memory) + { + // Construct action to deposit tokens held by this contract into dydx. + IDydx.AssetAmount memory amountToDeposit = IDydx.AssetAmount({ + sign: true, // true if positive. + denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account. + ref: IDydx.AssetReference.Target, // Target => an absolute amount. + value: amount // amount to deposit. + }); + + IDydx.ActionArgs memory depositAction = IDydx.ActionArgs({ + actionType: IDydx.ActionType.Deposit, // deposit tokens. + amount: amountToDeposit, // amount to deposit. + accountId: 0, // index in the `accounts` when calling `operate` below. + primaryMarketId: bridgeData.dydxFromMarketId, // indicates which token to deposit. + otherAddress: depositFrom, // deposit tokens from `this` address. + // unused parameters + secondaryMarketId: 0, + otherAccountId: 0, + data: hex'' + }); + + return depositAction; + } + + /// @dev Returns a dydx `WithdrawAction`. + function _createWithdrawAction( + address withdrawTo, + uint256 amount, + BridgeData memory bridgeData + ) + internal + pure + returns (IDydx.ActionArgs memory) + { + // Construct action to withdraw tokens from dydx into `to`. + IDydx.AssetAmount memory amountToWithdraw = IDydx.AssetAmount({ + sign: true, // true if positive. + denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account. + ref: IDydx.AssetReference.Target, // Target => an absolute amount. + value: amount // amount to withdraw. + }); + + IDydx.ActionArgs memory withdrawAction = IDydx.ActionArgs({ + actionType: IDydx.ActionType.Withdraw, // withdraw tokens. + amount: amountToWithdraw, // amount to withdraw. + accountId: 0, // index in the `accounts` when calling `operate` below. + primaryMarketId: bridgeData.dydxToMarketId, // indicates which token to withdraw. + otherAddress: withdrawTo, // withdraw tokens to `to` address. + // unused parameters + secondaryMarketId: 0, + otherAccountId: 0, + data: hex'' + }); + + return withdrawAction; + } + + /// @dev Given a 0x order where the `makerAssetData` corresponds to a dydx transfer via this bridge, + /// `isValidSignature` verifies that the corresponding dydx account owner (or operator) + /// has authorized the trade by signing the input order. + /// @param data Signed tuple (ZeroExOrder, hash(ZeroExOrder)) + /// @param signature Proof that `data` has been signed. + /// @return bytes4(0x20c13b0b) if the signature check succeeds. + function isValidSignature( + bytes calldata data, + bytes calldata signature + ) + external + view + returns (bytes4) + { + // Assert that `data` is an encoded `OrderWithHash`. + require( + data.readBytes4(0) == EIP1271_ORDER_WITH_HASH_SELECTOR, + "INVALID_DATA_EXPECTED_EIP1271_ORDER_WITH_HASH" + ); + + // Assert that signature is correct length. + require( + signature.length == 65, + "INVALID_SIGNATURE_LENGTH" + ); + + // Decode the order and hash, plus extract the dydxBridge asset data. + ( + LibOrder.Order memory order, + bytes32 orderHash + ) = abi.decode( + data.slice(4, data.length), + (LibOrder.Order, bytes32) + ); + + // Decode and validate the asset proxy id. + require( + order.makerAssetData.readBytes4(0) == IAssetData(address(0)).ERC20Bridge.selector, + "MAKER_ASSET_DATA_NOT_ENCODED_FOR_ERC20_BRIDGE" + ); + + // Decode the ERC20 Bridge asset data. + ( + /* address tokenAddress */, + address bridgeAddress, + bytes memory encodedBridgeData + ) = abi.decode( + order.makerAssetData.slice(4, order.makerAssetData.length), + (address, address, bytes) + ); + require( + bridgeAddress == address(this), + "INVALID_BRIDGE_ADDRESS" + ); + + // Decode and validate the `bridgeData` and extract the expected signer address. + (BridgeData memory bridgeData) = abi.decode(encodedBridgeData, (BridgeData)); + address signerAddress = bridgeData.dydxAccountOperator != address(0) + ? bridgeData.dydxAccountOperator + : bridgeData.dydxAccountOwner; + + // Validate signature. + address recovered = ecrecover( + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + orderHash + )), + uint8(signature[0]), // v + signature.readBytes32(1), // r + signature.readBytes32(33) // s + ); + + // Return `VALID_SIGNATURE_RETURN_VALUE` iff signature is valid. + return (signerAddress == recovered) + ? VALID_SIGNATURE_RETURN_VALUE + : INVALID_SIGNATURE_RETURN_VALUE; + } +} diff --git a/contracts/asset-proxy/contracts/src/interfaces/IDyDx.sol b/contracts/asset-proxy/contracts/src/interfaces/IDyDx.sol new file mode 100644 index 0000000000..b657538e28 --- /dev/null +++ b/contracts/asset-proxy/contracts/src/interfaces/IDyDx.sol @@ -0,0 +1,99 @@ +/* + + Copyright 2019 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.5.9; +pragma experimental ABIEncoderV2; + + +interface IDydx { + + /// @dev Represents the unique key that specifies an account + struct AccountInfo { + address owner; // The address that owns the account + uint256 number; // A nonce that allows a single address to control many accounts + } + + enum ActionType { + Deposit, // supply tokens + Withdraw, // borrow tokens + Transfer, // transfer balance between accounts + Buy, // buy an amount of some token (externally) + Sell, // sell an amount of some token (externally) + Trade, // trade tokens against another account + Liquidate, // liquidate an undercollateralized or expiring account + Vaporize, // use excess tokens to zero-out a completely negative account + Call // send arbitrary data to an address + } + + /// @dev Arguments that are passed to Solo in an ordered list as part of a single operation. + /// Each ActionArgs has an actionType which specifies which action struct that this data will be + /// parsed into before being processed. + struct ActionArgs { + ActionType actionType; + uint256 accountId; + AssetAmount amount; + uint256 primaryMarketId; + uint256 secondaryMarketId; + address otherAddress; + uint256 otherAccountId; + bytes data; + } + + enum AssetDenomination { + Wei, // the amount is denominated in wei + Par // the amount is denominated in par + } + + enum AssetReference { + Delta, // the amount is given as a delta from the current value + Target // the amount is given as an exact number to end up at + } + + struct AssetAmount { + bool sign; // true if positive + AssetDenomination denomination; + AssetReference ref; + uint256 value; + } + + /// @dev The main entry-point to Solo that allows users and contracts to manage accounts. + /// Take one or more actions on one or more accounts. The msg.sender must be the owner or + /// operator of all accounts except for those being liquidated, vaporized, or traded with. + /// One call to operate() is considered a singular "operation". Account collateralization is + /// ensured only after the completion of the entire operation. + /// @param accounts A list of all accounts that will be used in this operation. Cannot contain + /// duplicates. In each action, the relevant account will be referred-to by its + /// index in the list. + /// @param actions An ordered list of all actions that will be taken in this operation. The + /// actions will be processed in order. + function operate( + AccountInfo[] calldata accounts, + ActionArgs[] calldata actions + ) + external; + + /// @dev Get the ERC20 token address for a market. + /// @param marketId The market to query + /// @return The token address + function getMarketTokenAddress( + uint256 marketId + ) + external + view + returns (address); +} diff --git a/contracts/asset-proxy/contracts/test/TestDydxBridge.sol b/contracts/asset-proxy/contracts/test/TestDydxBridge.sol new file mode 100644 index 0000000000..334f992838 --- /dev/null +++ b/contracts/asset-proxy/contracts/test/TestDydxBridge.sol @@ -0,0 +1,35 @@ +/* + + Copyright 2019 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.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "../src/bridges/DydxBridge.sol"; + + +contract TestDydxBridge { + + function OrderWithHash( + LibOrder.Order calldata order, + bytes32 orderHash + ) + external + pure + {} +} \ No newline at end of file diff --git a/contracts/asset-proxy/package.json b/contracts/asset-proxy/package.json index 7602983f88..251bd3ddda 100644 --- a/contracts/asset-proxy/package.json +++ b/contracts/asset-proxy/package.json @@ -38,8 +38,13 @@ "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { +<<<<<<< HEAD "publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,ChaiBridge,TestStaticCallTarget", - "abis": "./test/generated-artifacts/@(ChaiBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json", + "abis": "./test/generated-artifacts/@(ChaiBridge|DyDxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IDyDx|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json", +======= + "publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,TestStaticCallTarget", + "abis": "./test/generated-artifacts/@(DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IDydx|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json", +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/asset-proxy/test/artifacts.ts b/contracts/asset-proxy/test/artifacts.ts index 1e9b719310..b9c26f15a5 100644 --- a/contracts/asset-proxy/test/artifacts.ts +++ b/contracts/asset-proxy/test/artifacts.ts @@ -5,7 +5,12 @@ */ import { ContractArtifact } from 'ethereum-types'; +<<<<<<< HEAD import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json'; +import * as DyDxBridge from '../test/generated-artifacts/DyDxBridge.json'; +======= +import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json'; +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json'; import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json'; import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.json'; @@ -15,7 +20,12 @@ import * as IAssetData from '../test/generated-artifacts/IAssetData.json'; import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json'; import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json'; import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json'; +<<<<<<< HEAD import * as IChai from '../test/generated-artifacts/IChai.json'; +import * as IDyDx from '../test/generated-artifacts/IDyDx.json'; +======= +import * as IDydx from '../test/generated-artifacts/IDydx.json'; +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json'; @@ -44,7 +54,12 @@ export const artifacts = { ERC721Proxy: ERC721Proxy as ContractArtifact, MultiAssetProxy: MultiAssetProxy as ContractArtifact, StaticCallProxy: StaticCallProxy as ContractArtifact, +<<<<<<< HEAD ChaiBridge: ChaiBridge as ContractArtifact, + DyDxBridge: DyDxBridge as ContractArtifact, +======= + DydxBridge: DydxBridge as ContractArtifact, +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` Eth2DaiBridge: Eth2DaiBridge as ContractArtifact, KyberBridge: KyberBridge as ContractArtifact, UniswapBridge: UniswapBridge as ContractArtifact, @@ -52,7 +67,12 @@ export const artifacts = { IAssetProxy: IAssetProxy as ContractArtifact, IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, IAuthorizable: IAuthorizable as ContractArtifact, +<<<<<<< HEAD IChai: IChai as ContractArtifact, + IDyDx: IDyDx as ContractArtifact, +======= + IDydx: IDydx as ContractArtifact, +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` IERC20Bridge: IERC20Bridge as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact, IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact, diff --git a/contracts/asset-proxy/test/dydx_bridge.ts b/contracts/asset-proxy/test/dydx_bridge.ts new file mode 100644 index 0000000000..76d7df0f0e --- /dev/null +++ b/contracts/asset-proxy/test/dydx_bridge.ts @@ -0,0 +1,120 @@ +import { + blockchainTests, + constants, + expect, + getRandomInteger, + hexLeftPad, + hexRandom, + OrderFactory, + orderHashUtils, + randomAddress, + verifyEventsFromLogs, +} from '@0x/contracts-test-utils'; +import { AssetProxyId } from '@0x/types'; +import { AbiEncoder, BigNumber } from '@0x/utils'; +import { DecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; +import * as ethUtil from 'ethereumjs-util'; + +import { artifacts } from './artifacts'; + +import { DydxBridgeContract, IAssetDataContract, TestDydxBridgeContract } from './wrappers'; + +blockchainTests.resets.only('Dydx unit tests', env => { + const dydxAccountNumber = new BigNumber(1); + const dydxFromMarketId = new BigNumber(2); + const dydxToMarketId = new BigNumber(3); + let testContract: DydxBridgeContract; + let owner: string; + let dydxAccountOwner: string; + let bridgeDataEncoder: AbiEncoder.DataType; + let eip1271Encoder: TestDydxBridgeContract; + let assetDataEncoder: IAssetDataContract; + let orderFactory: OrderFactory; + + before(async () => { + // Deploy dydx bridge + testContract = await DydxBridgeContract.deployFrom0xArtifactAsync( + artifacts.DydxBridge, + env.provider, + env.txDefaults, + artifacts, + ); + + // Get accounts + const accounts = await env.web3Wrapper.getAvailableAddressesAsync(); + [owner, dydxAccountOwner] = accounts; + const dydxAccountOwnerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(dydxAccountOwner)]; + + // Create order factory for dydx bridge + const chainId = await env.getChainIdAsync(); + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: testContract.address, + feeRecipientAddress: randomAddress(), + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + makerFeeAssetData: constants.NULL_BYTES, + takerFeeAssetData: constants.NULL_BYTES, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + exchangeAddress: constants.NULL_ADDRESS, + chainId, + }; + orderFactory = new OrderFactory(dydxAccountOwnerPrivateKey, defaultOrderParams); + + // Create encoder for Bridge Data + bridgeDataEncoder = AbiEncoder.create([ + {name: 'dydxAccountOwner', type: 'address'}, + {name: 'dydxAccountNumber', type: 'uint256'}, + {name: 'dydxAccountOperator', type: 'address'}, + {name: 'dydxFromMarketId', type: 'uint256'}, + {name: 'dydxToMarketId', type: 'uint256'}, + {name: 'shouldDepositIntoDydx', type: 'bool'}, + {name: 'fromTokenAddress', type: 'address'}, + ]); + + // Create encoders + assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); + eip1271Encoder = new TestDydxBridgeContract(constants.NULL_ADDRESS, env.provider); + }); + + describe('isValidSignature()', () => { + const SUCCESS_BYTES = '0x20c13b0b'; + + it('returns success bytes if signature is valid', async () => { + // Construct valid bridge data for dydx account owner + const bridgeData = { + dydxAccountOwner, + dydxAccountNumber, + dydxAccountOperator: constants.NULL_ADDRESS, + dydxFromMarketId, + dydxToMarketId, + shouldDepositIntoDydx: false, + fromTokenAddress: constants.NULL_ADDRESS, + }; + const encodedBridgeData = bridgeDataEncoder.encode(bridgeData); + + // Construct valid order from dydx account owner + const makerAssetData = assetDataEncoder + .ERC20Bridge( + constants.NULL_ADDRESS, + testContract.address, + encodedBridgeData + ) + .getABIEncodedTransactionData() + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + }); + const signedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + + // Encode `isValidSignature` parameters + const eip1271Data = eip1271Encoder.OrderWithHash(signedOrder, signedOrderHash).getABIEncodedTransactionData(); + const eip1271Signature = ethUtil.bufferToHex(ethUtil.toBuffer(signedOrder.signature).slice(0, 65)); // pop signature type from end + + // Validate signature + const result = await testContract.isValidSignature(eip1271Data, eip1271Signature).callAsync(); + expect(result).to.eq(SUCCESS_BYTES); + }); + }); +}); diff --git a/contracts/asset-proxy/test/wrappers.ts b/contracts/asset-proxy/test/wrappers.ts index 18085e0d6d..b226791757 100644 --- a/contracts/asset-proxy/test/wrappers.ts +++ b/contracts/asset-proxy/test/wrappers.ts @@ -3,7 +3,12 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +<<<<<<< HEAD export * from '../test/generated-wrappers/chai_bridge'; +export * from '../test/generated-wrappers/dy_dx_bridge'; +======= +export * from '../test/generated-wrappers/dydx_bridge'; +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` export * from '../test/generated-wrappers/erc1155_proxy'; export * from '../test/generated-wrappers/erc20_bridge_proxy'; export * from '../test/generated-wrappers/erc20_proxy'; @@ -13,7 +18,12 @@ export * from '../test/generated-wrappers/i_asset_data'; export * from '../test/generated-wrappers/i_asset_proxy'; export * from '../test/generated-wrappers/i_asset_proxy_dispatcher'; export * from '../test/generated-wrappers/i_authorizable'; +<<<<<<< HEAD export * from '../test/generated-wrappers/i_chai'; +export * from '../test/generated-wrappers/i_dy_dx'; +======= +export * from '../test/generated-wrappers/i_dydx'; +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` export * from '../test/generated-wrappers/i_erc20_bridge'; export * from '../test/generated-wrappers/i_eth2_dai'; export * from '../test/generated-wrappers/i_kyber_network_proxy'; diff --git a/contracts/asset-proxy/tsconfig.json b/contracts/asset-proxy/tsconfig.json index b7dfad1922..431d299ed4 100644 --- a/contracts/asset-proxy/tsconfig.json +++ b/contracts/asset-proxy/tsconfig.json @@ -16,7 +16,12 @@ "generated-artifacts/StaticCallProxy.json", "generated-artifacts/TestStaticCallTarget.json", "generated-artifacts/UniswapBridge.json", +<<<<<<< HEAD "test/generated-artifacts/ChaiBridge.json", + "test/generated-artifacts/DyDxBridge.json", +======= + "test/generated-artifacts/DydxBridge.json", +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` "test/generated-artifacts/ERC1155Proxy.json", "test/generated-artifacts/ERC20BridgeProxy.json", "test/generated-artifacts/ERC20Proxy.json", @@ -26,7 +31,12 @@ "test/generated-artifacts/IAssetProxy.json", "test/generated-artifacts/IAssetProxyDispatcher.json", "test/generated-artifacts/IAuthorizable.json", +<<<<<<< HEAD "test/generated-artifacts/IChai.json", + "test/generated-artifacts/IDyDx.json", +======= + "test/generated-artifacts/IDydx.json", +>>>>>>> ee82a0f67... Consistent capitalization for "dydx"` "test/generated-artifacts/IERC20Bridge.json", "test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IKyberNetworkProxy.json", diff --git a/contracts/utils/contracts/src/DeploymentConstants.sol b/contracts/utils/contracts/src/DeploymentConstants.sol index da250bdc4b..a0e50f5930 100644 --- a/contracts/utils/contracts/src/DeploymentConstants.sol +++ b/contracts/utils/contracts/src/DeploymentConstants.sol @@ -42,6 +42,8 @@ contract DeploymentConstants { address constant private DEV_UTILS_ADDRESS = 0xcCc2431a7335F21d9268bA62F0B32B0f2EFC463f; /// @dev Kyber ETH pseudo-address. address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev Mainnet address of the DyDx contract. + address constant private DYDX_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e; /// @dev Overridable way to get the `KyberNetworkProxy` address. /// @return kyberAddress The `IKyberNetworkProxy` address. @@ -122,4 +124,14 @@ contract DeploymentConstants { { return DEV_UTILS_ADDRESS; } + + /// @dev Overridable way to get the DyDx contract. + /// @return exchange The DyDx exchange contract. + function _getDyDxAddress() + internal + view + returns (address dydxAddress) + { + return DYDX_ADDRESS; + } }