Simplified the dydx bridge implememtation that does not use the bridge as the maker.
This commit is contained in:
@@ -1,120 +1,204 @@
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
getRandomInteger,
|
||||
hexLeftPad,
|
||||
hexRandom,
|
||||
OrderFactory,
|
||||
orderHashUtils,
|
||||
randomAddress,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { AuthorizableRevertErrors } from '@0x/contracts-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 { TestDydxBridgeContract, TestDydxBridgeEvents } from './wrappers';
|
||||
|
||||
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;
|
||||
blockchainTests.resets('DydxBridge unit tests', env => {
|
||||
const accountNumber = new BigNumber(1);
|
||||
const marketId = new BigNumber(2);
|
||||
let testContract: TestDydxBridgeContract;
|
||||
let owner: string;
|
||||
let dydxAccountOwner: string;
|
||||
let bridgeDataEncoder: AbiEncoder.DataType;
|
||||
let eip1271Encoder: TestDydxBridgeContract;
|
||||
let assetDataEncoder: IAssetDataContract;
|
||||
let orderFactory: OrderFactory;
|
||||
let authorized: string;
|
||||
let notAuthorized: string;
|
||||
let accountOwner: string;
|
||||
let accountOperator: string;
|
||||
let notAccountOwnerNorOperator: string;
|
||||
let receiver: string;
|
||||
|
||||
before(async () => {
|
||||
// Get accounts
|
||||
const accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
[
|
||||
owner,
|
||||
authorized,
|
||||
notAuthorized,
|
||||
accountOwner,
|
||||
accountOperator,
|
||||
notAccountOwnerNorOperator,
|
||||
receiver,
|
||||
] = accounts;
|
||||
|
||||
// Deploy dydx bridge
|
||||
testContract = await DydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DydxBridge,
|
||||
testContract = await TestDydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestDydxBridge,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
accountOperator,
|
||||
);
|
||||
|
||||
// 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);
|
||||
// Authorize `authorized` account on `testContract`.
|
||||
await testContract.addAuthorizedAddress(authorized).awaitTransactionSuccessAsync({ from: owner });
|
||||
});
|
||||
|
||||
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(
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
interface BridgeData {
|
||||
action: number;
|
||||
accountOwner: string;
|
||||
accountNumber: BigNumber;
|
||||
marketId: BigNumber;
|
||||
}
|
||||
enum DydxBridgeActions {
|
||||
Deposit,
|
||||
Withdraw,
|
||||
}
|
||||
let defaultBridgeData: any;
|
||||
let bridgeDataEncoder: AbiEncoder.DataType;
|
||||
const callBridgeTransferFrom = async (
|
||||
from: string,
|
||||
bridgeData: BridgeData,
|
||||
sender: string,
|
||||
): Promise<string> => {
|
||||
const returnValue = await testContract
|
||||
.bridgeTransferFrom(
|
||||
constants.NULL_ADDRESS,
|
||||
testContract.address,
|
||||
encodedBridgeData
|
||||
from,
|
||||
receiver,
|
||||
new BigNumber(1),
|
||||
bridgeDataEncoder.encode(bridgeData),
|
||||
)
|
||||
.getABIEncodedTransactionData()
|
||||
const signedOrder = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetData,
|
||||
});
|
||||
const signedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
.callAsync({ from: sender });
|
||||
return returnValue;
|
||||
};
|
||||
const callBridgeTransferFromAndVerifyEvents = async (
|
||||
actionType: number,
|
||||
actionAddress: string,
|
||||
from: string,
|
||||
bridgeData: BridgeData,
|
||||
sender: string,
|
||||
): Promise<void> => {
|
||||
// Execute transaction.
|
||||
const txReceipt = await testContract
|
||||
.bridgeTransferFrom(
|
||||
constants.NULL_ADDRESS,
|
||||
from,
|
||||
receiver,
|
||||
new BigNumber(1),
|
||||
bridgeDataEncoder.encode(bridgeData),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: sender });
|
||||
|
||||
// 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
|
||||
// Verify `OperateAccount` event.
|
||||
verifyEventsFromLogs(
|
||||
txReceipt.logs,
|
||||
[
|
||||
{
|
||||
owner: accountOwner,
|
||||
number: accountNumber,
|
||||
},
|
||||
],
|
||||
TestDydxBridgeEvents.OperateAccount,
|
||||
);
|
||||
|
||||
// Validate signature
|
||||
const result = await testContract.isValidSignature(eip1271Data, eip1271Signature).callAsync();
|
||||
expect(result).to.eq(SUCCESS_BYTES);
|
||||
// Verify `OperateAction` event.
|
||||
const accountId = new BigNumber(0);
|
||||
const positiveAmountSign = true;
|
||||
const weiDenomination = 0;
|
||||
const absoluteAmountRef = 1;
|
||||
verifyEventsFromLogs(
|
||||
txReceipt.logs,
|
||||
[
|
||||
{
|
||||
actionType,
|
||||
accountId,
|
||||
amountSign: positiveAmountSign,
|
||||
amountDenomination: weiDenomination,
|
||||
amountRef: absoluteAmountRef,
|
||||
amountValue: new BigNumber(1),
|
||||
primaryMarketId: marketId,
|
||||
secondaryMarketId: constants.ZERO_AMOUNT,
|
||||
otherAddress: actionAddress,
|
||||
otherAccountId: constants.ZERO_AMOUNT,
|
||||
data: '0x',
|
||||
},
|
||||
],
|
||||
TestDydxBridgeEvents.OperateAction,
|
||||
);
|
||||
};
|
||||
before(async () => {
|
||||
// Construct default bridge data
|
||||
defaultBridgeData = {
|
||||
action: DydxBridgeActions.Deposit as number,
|
||||
accountOwner,
|
||||
accountNumber,
|
||||
marketId,
|
||||
};
|
||||
|
||||
// Create encoder for bridge data
|
||||
bridgeDataEncoder = AbiEncoder.create([
|
||||
{ name: 'action', type: 'uint8' },
|
||||
{ name: 'accountOwner', type: 'address' },
|
||||
{ name: 'accountNumber', type: 'uint256' },
|
||||
{ name: 'marketId', type: 'uint256' },
|
||||
]);
|
||||
});
|
||||
it('succeeds if `from` owns the dydx account', async () => {
|
||||
await callBridgeTransferFrom(accountOwner, defaultBridgeData, authorized);
|
||||
});
|
||||
it('succeeds if `from` operates the dydx account', async () => {
|
||||
await callBridgeTransferFrom(accountOperator, defaultBridgeData, authorized);
|
||||
});
|
||||
it('reverts if `from` is neither the owner nor the operator of the dydx account', async () => {
|
||||
const tx = callBridgeTransferFrom(notAccountOwnerNorOperator, defaultBridgeData, authorized);
|
||||
const expectedError = 'INVALID_DYDX_OWNER_OR_OPERATOR';
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action', async () => {
|
||||
const depositAction = 0;
|
||||
const depositFrom = accountOwner;
|
||||
const bridgeData = {
|
||||
...defaultBridgeData,
|
||||
action: depositAction,
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(
|
||||
depositAction,
|
||||
depositFrom,
|
||||
accountOwner,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action', async () => {
|
||||
const withdrawAction = 1;
|
||||
const withdrawTo = receiver;
|
||||
const bridgeData = {
|
||||
...defaultBridgeData,
|
||||
action: withdrawAction,
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(
|
||||
withdrawAction,
|
||||
withdrawTo,
|
||||
accountOwner,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('reverts if called by an unauthorized account', async () => {
|
||||
const callBridgeTransferFromPromise = callBridgeTransferFrom(
|
||||
accountOwner,
|
||||
defaultBridgeData,
|
||||
notAuthorized,
|
||||
);
|
||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
||||
return expect(callBridgeTransferFromPromise).to.revertWith(expectedError);
|
||||
});
|
||||
it('should return magic bytes if call succeeds', async () => {
|
||||
const returnValue = await callBridgeTransferFrom(accountOwner, defaultBridgeData, authorized);
|
||||
expect(returnValue).to.equal(AssetProxyId.ERC20Bridge);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user