400 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { LibMathRevertErrors } from '@0x/contracts-exchange-libs';
 | |
| import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
 | |
| import { AssetProxyId, RevertReason } from '@0x/types';
 | |
| import { BigNumber } from '@0x/utils';
 | |
| import * as _ from 'lodash';
 | |
| 
 | |
| import { DydxBridgeActionType, DydxBridgeData, dydxBridgeDataEncoder } from '../src/dydx_bridge_encoder';
 | |
| import { ERC20BridgeProxyContract, IAssetDataContract } from '../src/wrappers';
 | |
| 
 | |
| import { artifacts } from './artifacts';
 | |
| import { TestDydxBridgeContract, TestDydxBridgeEvents } from './wrappers';
 | |
| 
 | |
| blockchainTests.resets('DydxBridge unit tests', env => {
 | |
|     const defaultAccountNumber = new BigNumber(1);
 | |
|     const marketId = new BigNumber(2);
 | |
|     const defaultAmount = new BigNumber(4);
 | |
|     const notAuthorized = '0x0000000000000000000000000000000000000001';
 | |
|     const defaultDepositAction = {
 | |
|         actionType: DydxBridgeActionType.Deposit,
 | |
|         accountIdx: constants.ZERO_AMOUNT,
 | |
|         marketId,
 | |
|         conversionRateNumerator: constants.ZERO_AMOUNT,
 | |
|         conversionRateDenominator: constants.ZERO_AMOUNT,
 | |
|     };
 | |
|     const defaultWithdrawAction = {
 | |
|         actionType: DydxBridgeActionType.Withdraw,
 | |
|         accountIdx: constants.ZERO_AMOUNT,
 | |
|         marketId,
 | |
|         conversionRateNumerator: constants.ZERO_AMOUNT,
 | |
|         conversionRateDenominator: constants.ZERO_AMOUNT,
 | |
|     };
 | |
|     let testContract: TestDydxBridgeContract;
 | |
|     let testProxyContract: ERC20BridgeProxyContract;
 | |
|     let assetDataEncoder: IAssetDataContract;
 | |
|     let owner: string;
 | |
|     let authorized: string;
 | |
|     let accountOwner: string;
 | |
|     let receiver: string;
 | |
| 
 | |
|     before(async () => {
 | |
|         // Get accounts
 | |
|         const accounts = await env.web3Wrapper.getAvailableAddressesAsync();
 | |
|         [owner, authorized, accountOwner, receiver] = accounts;
 | |
| 
 | |
|         // Deploy dydx bridge
 | |
|         testContract = await TestDydxBridgeContract.deployFrom0xArtifactAsync(
 | |
|             artifacts.TestDydxBridge,
 | |
|             env.provider,
 | |
|             env.txDefaults,
 | |
|             artifacts,
 | |
|             [accountOwner, receiver],
 | |
|         );
 | |
| 
 | |
|         // Deploy test erc20 bridge proxy
 | |
|         testProxyContract = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync(
 | |
|             artifacts.ERC20BridgeProxy,
 | |
|             env.provider,
 | |
|             env.txDefaults,
 | |
|             artifacts,
 | |
|         );
 | |
|         await testProxyContract.addAuthorizedAddress(authorized).awaitTransactionSuccessAsync({ from: owner });
 | |
| 
 | |
|         // Setup asset data encoder
 | |
|         assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
 | |
|     });
 | |
| 
 | |
|     describe('bridgeTransferFrom()', () => {
 | |
|         const callBridgeTransferFrom = async (
 | |
|             from: string,
 | |
|             to: string,
 | |
|             amount: BigNumber,
 | |
|             bridgeData: DydxBridgeData,
 | |
|             sender: string,
 | |
|         ): Promise<string> => {
 | |
|             const returnValue = await testContract
 | |
|                 .bridgeTransferFrom(
 | |
|                     constants.NULL_ADDRESS,
 | |
|                     from,
 | |
|                     to,
 | |
|                     amount,
 | |
|                     dydxBridgeDataEncoder.encode({ bridgeData }),
 | |
|                 )
 | |
|                 .callAsync({ from: sender });
 | |
|             return returnValue;
 | |
|         };
 | |
|         const executeBridgeTransferFromAndVerifyEvents = async (
 | |
|             from: string,
 | |
|             to: string,
 | |
|             amount: BigNumber,
 | |
|             bridgeData: DydxBridgeData,
 | |
|             sender: string,
 | |
|         ): Promise<void> => {
 | |
|             // Execute transaction.
 | |
|             const txReceipt = await testContract
 | |
|                 .bridgeTransferFrom(
 | |
|                     constants.NULL_ADDRESS,
 | |
|                     from,
 | |
|                     to,
 | |
|                     amount,
 | |
|                     dydxBridgeDataEncoder.encode({ bridgeData }),
 | |
|                 )
 | |
|                 .awaitTransactionSuccessAsync({ from: sender });
 | |
| 
 | |
|             // Verify `OperateAccount` event.
 | |
|             const expectedOperateAccountEvents = [];
 | |
|             for (const accountNumber of bridgeData.accountNumbers) {
 | |
|                 expectedOperateAccountEvents.push({
 | |
|                     owner: accountOwner,
 | |
|                     number: accountNumber,
 | |
|                 });
 | |
|             }
 | |
|             verifyEventsFromLogs(txReceipt.logs, expectedOperateAccountEvents, TestDydxBridgeEvents.OperateAccount);
 | |
| 
 | |
|             // Verify `OperateAction` event.
 | |
|             const weiDenomination = 0;
 | |
|             const deltaAmountRef = 0;
 | |
|             const expectedOperateActionEvents = [];
 | |
|             for (const action of bridgeData.actions) {
 | |
|                 expectedOperateActionEvents.push({
 | |
|                     actionType: action.actionType as number,
 | |
|                     accountIdx: action.accountIdx,
 | |
|                     amountSign: action.actionType === DydxBridgeActionType.Deposit ? true : false,
 | |
|                     amountDenomination: weiDenomination,
 | |
|                     amountRef: deltaAmountRef,
 | |
|                     amountValue: action.conversionRateDenominator.gt(0)
 | |
|                         ? amount
 | |
|                               .times(action.conversionRateNumerator)
 | |
|                               .dividedToIntegerBy(action.conversionRateDenominator)
 | |
|                         : amount,
 | |
|                     primaryMarketId: marketId,
 | |
|                     secondaryMarketId: constants.ZERO_AMOUNT,
 | |
|                     otherAddress: action.actionType === DydxBridgeActionType.Deposit ? from : to,
 | |
|                     otherAccountId: constants.ZERO_AMOUNT,
 | |
|                     data: '0x',
 | |
|                 });
 | |
|             }
 | |
|             verifyEventsFromLogs(txReceipt.logs, expectedOperateActionEvents, TestDydxBridgeEvents.OperateAction);
 | |
|         };
 | |
|         it('succeeds when calling with zero amount', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 constants.ZERO_AMOUNT,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling with no accounts', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling with no actions', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with the `withdraw` action and a single account', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultWithdrawAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
 | |
|                 actions: [defaultWithdrawAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
 | |
|                 actions: [defaultWithdrawAction, defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when calling `operate` with multiple actions under a single account', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultWithdrawAction, defaultDepositAction],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when scaling the `amount` to deposit', async () => {
 | |
|             const conversionRateNumerator = new BigNumber(1);
 | |
|             const conversionRateDenominator = new BigNumber(2);
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [
 | |
|                     defaultWithdrawAction,
 | |
|                     {
 | |
|                         ...defaultDepositAction,
 | |
|                         conversionRateNumerator,
 | |
|                         conversionRateDenominator,
 | |
|                     },
 | |
|                 ],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('succeeds when scaling the `amount` to withdraw', async () => {
 | |
|             const conversionRateNumerator = new BigNumber(1);
 | |
|             const conversionRateDenominator = new BigNumber(2);
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [
 | |
|                     defaultDepositAction,
 | |
|                     {
 | |
|                         ...defaultWithdrawAction,
 | |
|                         conversionRateNumerator,
 | |
|                         conversionRateDenominator,
 | |
|                     },
 | |
|                 ],
 | |
|             };
 | |
|             await executeBridgeTransferFromAndVerifyEvents(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|         });
 | |
|         it('reverts if not called by the ERC20 Bridge Proxy', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             const callBridgeTransferFromPromise = callBridgeTransferFrom(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 notAuthorized,
 | |
|             );
 | |
|             const expectedError = RevertReason.DydxBridgeOnlyCallableByErc20BridgeProxy;
 | |
|             return expect(callBridgeTransferFromPromise).to.revertWith(expectedError);
 | |
|         });
 | |
|         it('should return magic bytes if call succeeds', async () => {
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             const returnValue = await callBridgeTransferFrom(
 | |
|                 accountOwner,
 | |
|                 receiver,
 | |
|                 defaultAmount,
 | |
|                 bridgeData,
 | |
|                 authorized,
 | |
|             );
 | |
|             expect(returnValue).to.equal(AssetProxyId.ERC20Bridge);
 | |
|         });
 | |
|         it('should revert when `Operate` reverts', async () => {
 | |
|             // Set revert flag.
 | |
|             await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync();
 | |
| 
 | |
|             // Execute transfer.
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [defaultDepositAction],
 | |
|             };
 | |
|             const tx = callBridgeTransferFrom(accountOwner, receiver, defaultAmount, bridgeData, authorized);
 | |
|             const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE';
 | |
|             return expect(tx).to.revertWith(expectedError);
 | |
|         });
 | |
|         it('should revert when there is a rounding error', async () => {
 | |
|             // Setup a rounding error
 | |
|             const conversionRateNumerator = new BigNumber(5318);
 | |
|             const conversionRateDenominator = new BigNumber(47958);
 | |
|             const amount = new BigNumber(9000);
 | |
|             const bridgeData = {
 | |
|                 accountNumbers: [defaultAccountNumber],
 | |
|                 actions: [
 | |
|                     defaultDepositAction,
 | |
|                     {
 | |
|                         ...defaultWithdrawAction,
 | |
|                         conversionRateNumerator,
 | |
|                         conversionRateDenominator,
 | |
|                     },
 | |
|                 ],
 | |
|             };
 | |
| 
 | |
|             // Execute transfer and assert error.
 | |
|             const tx = callBridgeTransferFrom(accountOwner, receiver, amount, bridgeData, authorized);
 | |
|             const expectedError = new LibMathRevertErrors.RoundingError(
 | |
|                 conversionRateNumerator,
 | |
|                 conversionRateDenominator,
 | |
|                 amount,
 | |
|             );
 | |
|             return expect(tx).to.revertWith(expectedError);
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe('ERC20BridgeProxy.transferFrom()', () => {
 | |
|         const bridgeData = {
 | |
|             accountNumbers: [defaultAccountNumber],
 | |
|             actions: [defaultWithdrawAction],
 | |
|         };
 | |
|         let assetData: string;
 | |
| 
 | |
|         before(async () => {
 | |
|             const testTokenAddress = await testContract.getTestToken().callAsync();
 | |
|             assetData = assetDataEncoder
 | |
|                 .ERC20Bridge(testTokenAddress, testContract.address, dydxBridgeDataEncoder.encode({ bridgeData }))
 | |
|                 .getABIEncodedTransactionData();
 | |
|         });
 | |
| 
 | |
|         it('should succeed if `bridgeTransferFrom` succeeds', async () => {
 | |
|             await testProxyContract
 | |
|                 .transferFrom(assetData, accountOwner, receiver, defaultAmount)
 | |
|                 .awaitTransactionSuccessAsync({ from: authorized });
 | |
|         });
 | |
|         it('should revert if `bridgeTransferFrom` reverts', async () => {
 | |
|             // Set revert flag.
 | |
|             await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync();
 | |
|             const tx = testProxyContract
 | |
|                 .transferFrom(assetData, accountOwner, receiver, defaultAmount)
 | |
|                 .awaitTransactionSuccessAsync({ from: authorized });
 | |
|             const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE';
 | |
|             return expect(tx).to.revertWith(expectedError);
 | |
|         });
 | |
|     });
 | |
| });
 |