DyDx bridge implementation using contract as maker with signature validation.
This commit is contained in:
293
contracts/asset-proxy/contracts/src/bridges/DyDxBridge.sol
Normal file
293
contracts/asset-proxy/contracts/src/bridges/DyDxBridge.sol
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
99
contracts/asset-proxy/contracts/src/interfaces/IDyDx.sol
Normal file
99
contracts/asset-proxy/contracts/src/interfaces/IDyDx.sol
Normal file
@@ -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);
|
||||
}
|
||||
35
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
35
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
@@ -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
|
||||
{}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
120
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
120
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user