Simplified the dydx bridge implememtation that does not use the bridge as the maker.

This commit is contained in:
Greg Hysen
2019-11-22 20:51:25 -08:00
parent 56cbb69401
commit 444125a7e1
8 changed files with 495 additions and 402 deletions

View File

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