1171 lines
56 KiB
TypeScript
1171 lines
56 KiB
TypeScript
import {
|
|
DydxBridgeAction,
|
|
DydxBridgeActionType,
|
|
DydxBridgeData,
|
|
dydxBridgeDataEncoder,
|
|
IAssetDataContract,
|
|
} from '@0x/contracts-asset-proxy';
|
|
import {
|
|
blockchainTests,
|
|
constants,
|
|
expect,
|
|
getRandomFloat,
|
|
getRandomInteger,
|
|
Numberish,
|
|
randomAddress,
|
|
} from '@0x/contracts-test-utils';
|
|
import { Order } from '@0x/types';
|
|
import { BigNumber, fromTokenUnitAmount, toTokenUnitAmount } from '@0x/utils';
|
|
|
|
import { artifacts as devUtilsArtifacts } from './artifacts';
|
|
import { TestDydxContract, TestLibDydxBalanceContract } from './wrappers';
|
|
|
|
blockchainTests('LibDydxBalance', env => {
|
|
interface TestDydxConfig {
|
|
marginRatio: BigNumber;
|
|
operators: Array<{
|
|
owner: string;
|
|
operator: string;
|
|
}>;
|
|
accounts: Array<{
|
|
owner: string;
|
|
accountId: BigNumber;
|
|
balances: BigNumber[];
|
|
}>;
|
|
markets: Array<{
|
|
token: string;
|
|
decimals: number;
|
|
price: BigNumber;
|
|
}>;
|
|
}
|
|
|
|
const MARGIN_RATIO = 1.5;
|
|
const PRICE_DECIMALS = 18;
|
|
const MAKER_DECIMALS = 6;
|
|
const TAKER_DECIMALS = 18;
|
|
const INITIAL_TAKER_TOKEN_BALANCE = fromTokenUnitAmount(1000, TAKER_DECIMALS);
|
|
const BRIDGE_ADDRESS = randomAddress();
|
|
const ACCOUNT_OWNER = randomAddress();
|
|
const MAKER_PRICE = 150;
|
|
const TAKER_PRICE = 100;
|
|
const SOLVENT_ACCOUNT_IDX = 0;
|
|
// const MIN_SOLVENT_ACCOUNT_IDX = 1;
|
|
const INSOLVENT_ACCOUNT_IDX = 2;
|
|
const ZERO_BALANCE_ACCOUNT_IDX = 3;
|
|
const DYDX_CONFIG: TestDydxConfig = {
|
|
marginRatio: fromTokenUnitAmount(MARGIN_RATIO - 1, PRICE_DECIMALS),
|
|
operators: [{ owner: ACCOUNT_OWNER, operator: BRIDGE_ADDRESS }],
|
|
accounts: [
|
|
{
|
|
owner: ACCOUNT_OWNER,
|
|
accountId: getRandomInteger(1, 2 ** 64),
|
|
// Account exceeds collateralization.
|
|
balances: [fromTokenUnitAmount(10, TAKER_DECIMALS), fromTokenUnitAmount(-1, MAKER_DECIMALS)],
|
|
},
|
|
{
|
|
owner: ACCOUNT_OWNER,
|
|
accountId: getRandomInteger(1, 2 ** 64),
|
|
// Account is at minimum collateralization.
|
|
balances: [
|
|
fromTokenUnitAmount((MAKER_PRICE / TAKER_PRICE) * MARGIN_RATIO * 5, TAKER_DECIMALS),
|
|
fromTokenUnitAmount(-5, MAKER_DECIMALS),
|
|
],
|
|
},
|
|
{
|
|
owner: ACCOUNT_OWNER,
|
|
accountId: getRandomInteger(1, 2 ** 64),
|
|
// Account is undercollateralized..
|
|
balances: [fromTokenUnitAmount(1, TAKER_DECIMALS), fromTokenUnitAmount(-2, MAKER_DECIMALS)],
|
|
},
|
|
{
|
|
owner: ACCOUNT_OWNER,
|
|
accountId: getRandomInteger(1, 2 ** 64),
|
|
// Account has no balance.
|
|
balances: [fromTokenUnitAmount(0, TAKER_DECIMALS), fromTokenUnitAmount(0, MAKER_DECIMALS)],
|
|
},
|
|
],
|
|
markets: [
|
|
{
|
|
token: constants.NULL_ADDRESS, // TBD
|
|
decimals: TAKER_DECIMALS,
|
|
price: fromTokenUnitAmount(TAKER_PRICE, PRICE_DECIMALS),
|
|
},
|
|
{
|
|
token: constants.NULL_ADDRESS, // TBD
|
|
decimals: MAKER_DECIMALS,
|
|
price: fromTokenUnitAmount(MAKER_PRICE, PRICE_DECIMALS),
|
|
},
|
|
],
|
|
};
|
|
|
|
let dydx: TestDydxContract;
|
|
let testContract: TestLibDydxBalanceContract;
|
|
let assetDataContract: IAssetDataContract;
|
|
let takerTokenAddress: string;
|
|
let makerTokenAddress: string;
|
|
|
|
before(async () => {
|
|
assetDataContract = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
|
|
|
testContract = await TestLibDydxBalanceContract.deployWithLibrariesFrom0xArtifactAsync(
|
|
devUtilsArtifacts.TestLibDydxBalance,
|
|
devUtilsArtifacts,
|
|
env.provider,
|
|
env.txDefaults,
|
|
{},
|
|
);
|
|
|
|
// Create tokens.
|
|
takerTokenAddress = await testContract.createToken(TAKER_DECIMALS).callAsync();
|
|
await testContract.createToken(TAKER_DECIMALS).awaitTransactionSuccessAsync();
|
|
makerTokenAddress = await testContract.createToken(MAKER_DECIMALS).callAsync();
|
|
await testContract.createToken(MAKER_DECIMALS).awaitTransactionSuccessAsync();
|
|
|
|
DYDX_CONFIG.markets[0].token = takerTokenAddress;
|
|
DYDX_CONFIG.markets[1].token = makerTokenAddress;
|
|
|
|
dydx = await TestDydxContract.deployFrom0xArtifactAsync(
|
|
devUtilsArtifacts.TestDydx,
|
|
env.provider,
|
|
env.txDefaults,
|
|
{},
|
|
DYDX_CONFIG,
|
|
);
|
|
|
|
// Mint taker tokens.
|
|
await testContract
|
|
.setTokenBalance(takerTokenAddress, ACCOUNT_OWNER, INITIAL_TAKER_TOKEN_BALANCE)
|
|
.awaitTransactionSuccessAsync();
|
|
// Approve the Dydx contract to spend takerToken.
|
|
await testContract
|
|
.setTokenApproval(takerTokenAddress, ACCOUNT_OWNER, dydx.address, constants.MAX_UINT256)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
|
|
interface BalanceCheckInfo {
|
|
dydx: string;
|
|
bridgeAddress: string;
|
|
makerAddress: string;
|
|
makerTokenAddress: string;
|
|
takerTokenAddress: string;
|
|
orderMakerToTakerRate: BigNumber;
|
|
accounts: BigNumber[];
|
|
actions: DydxBridgeAction[];
|
|
}
|
|
|
|
function createBalanceCheckInfo(fields: Partial<BalanceCheckInfo> = {}): BalanceCheckInfo {
|
|
return {
|
|
dydx: dydx.address,
|
|
bridgeAddress: BRIDGE_ADDRESS,
|
|
makerAddress: ACCOUNT_OWNER,
|
|
makerTokenAddress: DYDX_CONFIG.markets[1].token,
|
|
takerTokenAddress: DYDX_CONFIG.markets[0].token,
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(5, MAKER_DECIMALS)),
|
|
),
|
|
accounts: [DYDX_CONFIG.accounts[SOLVENT_ACCOUNT_IDX].accountId],
|
|
actions: [],
|
|
...fields,
|
|
};
|
|
}
|
|
|
|
function getFilledAccountCollateralizations(
|
|
config: TestDydxConfig,
|
|
checkInfo: BalanceCheckInfo,
|
|
makerAssetFillAmount: BigNumber,
|
|
): BigNumber[] {
|
|
const values: BigNumber[][] = checkInfo.accounts.map((accountId, accountIdx) => {
|
|
const accountBalances = config.accounts[accountIdx].balances.slice();
|
|
for (const action of checkInfo.actions) {
|
|
const actionMarketId = action.marketId.toNumber();
|
|
const actionAccountIdx = action.accountIdx.toNumber();
|
|
if (checkInfo.accounts[actionAccountIdx] !== accountId) {
|
|
continue;
|
|
}
|
|
const rate = action.conversionRateDenominator.eq(0)
|
|
? new BigNumber(1)
|
|
: action.conversionRateNumerator.div(action.conversionRateDenominator);
|
|
const change = makerAssetFillAmount.times(
|
|
action.actionType === DydxBridgeActionType.Deposit ? rate : rate.negated(),
|
|
);
|
|
accountBalances[actionMarketId] = change.plus(accountBalances[actionMarketId]);
|
|
}
|
|
return accountBalances.map((b, marketId) =>
|
|
toTokenUnitAmount(b, config.markets[marketId].decimals).times(
|
|
toTokenUnitAmount(config.markets[marketId].price, PRICE_DECIMALS),
|
|
),
|
|
);
|
|
});
|
|
return values
|
|
.map(accountValues => {
|
|
return [
|
|
// supply
|
|
BigNumber.sum(...accountValues.filter(b => b.gte(0))),
|
|
// borrow
|
|
BigNumber.sum(...accountValues.filter(b => b.lt(0))).abs(),
|
|
];
|
|
})
|
|
.map(([supply, borrow]) => supply.div(borrow));
|
|
}
|
|
|
|
function getRandomRate(): BigNumber {
|
|
return getRandomFloat(0, 1);
|
|
}
|
|
|
|
// Computes a deposit rate that is the minimum to keep an account solvent
|
|
// perpetually.
|
|
function getBalancedDepositRate(withdrawRate: BigNumber, scaling: Numberish = 1): BigNumber {
|
|
// Add a small amount to the margin ratio to stay just above insolvency.
|
|
return withdrawRate.times((MAKER_PRICE / TAKER_PRICE) * (MARGIN_RATIO + 1.1e-4)).times(scaling);
|
|
}
|
|
|
|
function takerToMakerAmount(takerAmount: BigNumber): BigNumber {
|
|
return takerAmount.times(new BigNumber(10).pow(MAKER_DECIMALS - TAKER_DECIMALS));
|
|
}
|
|
|
|
describe('_getSolventMakerAmount()', () => {
|
|
it('computes fillable amount for a solvent maker', async () => {
|
|
// Deposit collateral at a rate low enough to steadily reduce the
|
|
// withdraw account's collateralization ratio.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate, Math.random());
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// The collateralization ratio after filling `makerAssetFillAmount`
|
|
// should be exactly at `MARGIN_RATIO`.
|
|
const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
|
|
expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
|
|
});
|
|
|
|
it('computes fillable amount for a solvent maker with zero-sized deposits', async () => {
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = new BigNumber(0);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// The collateralization ratio after filling `makerAssetFillAmount`
|
|
// should be exactly at `MARGIN_RATIO`.
|
|
const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
|
|
expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
|
|
});
|
|
|
|
it('computes fillable amount for a solvent maker with no deposits', async () => {
|
|
const withdrawRate = getRandomRate();
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// The collateralization ratio after filling `makerAssetFillAmount`
|
|
// should be exactly at `MARGIN_RATIO`.
|
|
const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
|
|
expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
|
|
});
|
|
|
|
it('computes fillable amount for a solvent maker with multiple deposits', async () => {
|
|
// Deposit collateral at a rate low enough to steadily reduce the
|
|
// withdraw account's collateralization ratio.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate, Math.random());
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.75), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.25), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// The collateralization ratio after filling `makerAssetFillAmount`
|
|
// should be exactly at `MARGIN_RATIO`.
|
|
const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
|
|
expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
|
|
});
|
|
|
|
it('returns infinite amount for a perpetually solvent maker', async () => {
|
|
// Deposit collateral at a rate that keeps the withdraw account's
|
|
// collateralization ratio constant.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
// Deposit/Withdraw at a rate == marginRatio.
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('returns infinite amount for a perpetually solvent maker with multiple deposits', async () => {
|
|
// Deposit collateral at a rate that keeps the withdraw account's
|
|
// collateralization ratio constant.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
// Deposit/Withdraw at a rate == marginRatio.
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.25), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.75), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('does not count deposits to other accounts', async () => {
|
|
// Deposit collateral at a rate that keeps the withdraw account's
|
|
// collateralization ratio constant, BUT we split it in two deposits
|
|
// and one will go into a different account.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.5), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
// Deposit enough to balance out withdraw, but
|
|
// into a different account.
|
|
accountIdx: new BigNumber(1),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.5), TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('returns zero on an account that is under-collateralized', async () => {
|
|
// Even though the deposit rate is enough to meet the minimum collateralization ratio,
|
|
// the account is under-collateralized from the start, so cannot be filled.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
accounts: [DYDX_CONFIG.accounts[INSOLVENT_ACCOUNT_IDX].accountId],
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(0);
|
|
});
|
|
|
|
it(
|
|
'returns zero on an account that has no balance if deposit ' +
|
|
'to withdraw ratio is < the minimum collateralization rate',
|
|
async () => {
|
|
// If the deposit rate is not enough to meet the minimum collateralization ratio,
|
|
// the fillable maker amount is zero because it will become insolvent as soon as
|
|
// the withdraw occurs.
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate, 0.99);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
accounts: [DYDX_CONFIG.accounts[ZERO_BALANCE_ACCOUNT_IDX].accountId],
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(0);
|
|
},
|
|
);
|
|
|
|
it(
|
|
'returns infinite on an account that has no balance if deposit ' +
|
|
'to withdraw ratio is >= the minimum collateralization rate',
|
|
async () => {
|
|
const withdrawRate = getRandomRate();
|
|
const depositRate = getBalancedDepositRate(withdrawRate);
|
|
const checkInfo = createBalanceCheckInfo({
|
|
accounts: [DYDX_CONFIG.accounts[ZERO_BALANCE_ACCOUNT_IDX].accountId],
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
|
|
conversionRateDenominator: fromTokenUnitAmount(1),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
},
|
|
);
|
|
});
|
|
|
|
blockchainTests.resets('_getDepositableMakerAmount()', () => {
|
|
it('returns infinite if no deposit action', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
|
|
),
|
|
actions: [],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('returns infinite if deposit rate is zero', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
|
|
),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(0, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('returns infinite if taker tokens cover the deposit rate', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
|
|
),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(Math.random() * 0.1, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
|
|
});
|
|
|
|
it('returns correct amount if taker tokens only partially cover deposit rate', async () => {
|
|
// The taker tokens getting exchanged in will only partially cover the deposit.
|
|
const exchangeRate = 0.1;
|
|
const depositRate = Math.random() + exchangeRate;
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
|
|
),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// Compute the equivalent taker asset fill amount.
|
|
const takerAssetFillAmount = fromTokenUnitAmount(
|
|
toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
|
|
// Reduce the deposit rate by the exchange rate.
|
|
.times(depositRate - exchangeRate),
|
|
TAKER_DECIMALS,
|
|
);
|
|
// Which should equal the entire taker token balance of the account owner.
|
|
// We do some rounding to account for integer vs FP vs symbolic precision differences.
|
|
expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(5)).to.bignumber.eq(
|
|
toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(5),
|
|
);
|
|
});
|
|
|
|
it('returns correct amount if the taker asset not an ERC20', async () => {
|
|
const depositRate = 0.1;
|
|
const checkInfo = createBalanceCheckInfo({
|
|
// The `takerTokenAddress` will be zero if the asset is not an ERC20.
|
|
takerTokenAddress: constants.NULL_ADDRESS,
|
|
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(0.1, MAKER_DECIMALS)),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// Compute the equivalent taker asset fill amount.
|
|
const takerAssetFillAmount = fromTokenUnitAmount(
|
|
toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
|
|
// Reduce the deposit rate by the exchange rate.
|
|
.times(depositRate),
|
|
TAKER_DECIMALS,
|
|
);
|
|
// Which should equal the entire taker token balance of the account owner.
|
|
// We do some rounding to account for integer vs FP vs symbolic precision differences.
|
|
expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(6)).to.bignumber.eq(
|
|
toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(6),
|
|
);
|
|
});
|
|
|
|
it('returns the correct amount if taker:maker deposit rate is 1:1 and' + 'token != taker token', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
takerTokenAddress: randomAddress(),
|
|
// These amounts should be effectively ignored in the final computation
|
|
// because the token being deposited is not the taker token.
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
|
|
),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(1, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(takerToMakerAmount(INITIAL_TAKER_TOKEN_BALANCE));
|
|
});
|
|
|
|
it('returns the smallest viable maker amount with multiple deposits', async () => {
|
|
// The taker tokens getting exchanged in will only partially cover the deposit.
|
|
const exchangeRate = 0.1;
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(
|
|
fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
|
|
),
|
|
actions: [
|
|
// Technically, deposits of the same token are not allowed, but the
|
|
// check isn't done in this function so we'll do this to simulate
|
|
// two deposits to distinct tokens.
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(Math.random() + exchangeRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(Math.random() + exchangeRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
|
|
// Extract the deposit rates.
|
|
const depositRates = checkInfo.actions.map(a =>
|
|
toTokenUnitAmount(a.conversionRateNumerator, TAKER_DECIMALS).div(
|
|
toTokenUnitAmount(a.conversionRateDenominator, MAKER_DECIMALS),
|
|
),
|
|
);
|
|
// The largest deposit rate will result in the smallest maker asset fill amount.
|
|
const maxDepositRate = BigNumber.max(...depositRates);
|
|
// Compute the equivalent taker asset fill amounts.
|
|
const takerAssetFillAmount = fromTokenUnitAmount(
|
|
toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
|
|
// Reduce the deposit rate by the exchange rate.
|
|
.times(maxDepositRate.minus(exchangeRate)),
|
|
TAKER_DECIMALS,
|
|
);
|
|
// Which should equal the entire taker token balance of the account owner.
|
|
// We do some rounding to account for integer vs FP vs symbolic precision differences.
|
|
expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(5)).to.bignumber.eq(
|
|
toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(5),
|
|
);
|
|
});
|
|
|
|
it(
|
|
'returns zero if the maker has no taker tokens and the deposit rate is' + 'greater than the exchange rate',
|
|
async () => {
|
|
await testContract
|
|
.setTokenBalance(takerTokenAddress, ACCOUNT_OWNER, constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync();
|
|
// The taker tokens getting exchanged in will only partially cover the deposit.
|
|
const exchangeRate = 0.1;
|
|
const depositRate = Math.random() + exchangeRate;
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(0);
|
|
},
|
|
);
|
|
|
|
it(
|
|
'returns zero if dydx has no taker token allowance and the deposit rate is' +
|
|
'greater than the exchange rate',
|
|
async () => {
|
|
await testContract
|
|
.setTokenApproval(takerTokenAddress, ACCOUNT_OWNER, dydx.address, constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync();
|
|
// The taker tokens getting exchanged in will only partially cover the deposit.
|
|
const exchangeRate = 0.1;
|
|
const depositRate = Math.random() + exchangeRate;
|
|
const checkInfo = createBalanceCheckInfo({
|
|
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
],
|
|
});
|
|
const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
|
|
expect(makerAssetFillAmount).to.bignumber.eq(0);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('_areActionsWellFormed()', () => {
|
|
it('Returns false if no actions', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if there is an account index out of range in deposits', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
accounts: DYDX_CONFIG.accounts.slice(0, 2).map(a => a.accountId),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(2),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if a market is not unique among deposits', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if no withdraw at the end', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if a withdraw comes before a deposit', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if more than one withdraw', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if withdraw is not for maker token', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Returns false if withdraw is for an out of range account', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
accounts: DYDX_CONFIG.accounts.slice(0, 2).map(a => a.accountId),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(2),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.false();
|
|
});
|
|
|
|
it('Can return true if no deposit', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.true();
|
|
});
|
|
|
|
it('Can return true if no deposit', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.true();
|
|
});
|
|
|
|
it('Can return true with multiple deposits', async () => {
|
|
const checkInfo = createBalanceCheckInfo({
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
});
|
|
const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
|
|
expect(r).to.be.true();
|
|
});
|
|
});
|
|
|
|
function createERC20AssetData(tokenAddress: string): string {
|
|
return assetDataContract.ERC20Token(tokenAddress).getABIEncodedTransactionData();
|
|
}
|
|
|
|
function createERC721AssetData(tokenAddress: string, tokenId: BigNumber): string {
|
|
return assetDataContract.ERC721Token(tokenAddress, tokenId).getABIEncodedTransactionData();
|
|
}
|
|
|
|
function createBridgeAssetData(
|
|
makerTokenAddress_: string,
|
|
bridgeAddress: string,
|
|
data: Partial<DydxBridgeData> = {},
|
|
): string {
|
|
return assetDataContract
|
|
.ERC20Bridge(
|
|
makerTokenAddress_,
|
|
bridgeAddress,
|
|
dydxBridgeDataEncoder.encode({
|
|
bridgeData: {
|
|
accountNumbers: DYDX_CONFIG.accounts.slice(0, 1).map(a => a.accountId),
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Deposit,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: fromTokenUnitAmount(1, TAKER_DECIMALS),
|
|
conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
...data,
|
|
},
|
|
}),
|
|
)
|
|
.getABIEncodedTransactionData();
|
|
}
|
|
|
|
function createOrder(orderFields: Partial<Order> = {}): Order {
|
|
return {
|
|
chainId: 1,
|
|
exchangeAddress: randomAddress(),
|
|
salt: getRandomInteger(1, constants.MAX_UINT256),
|
|
expirationTimeSeconds: getRandomInteger(1, constants.MAX_UINT256),
|
|
feeRecipientAddress: randomAddress(),
|
|
makerAddress: ACCOUNT_OWNER,
|
|
takerAddress: constants.NULL_ADDRESS,
|
|
senderAddress: constants.NULL_ADDRESS,
|
|
makerFee: getRandomInteger(1, constants.MAX_UINT256),
|
|
takerFee: getRandomInteger(1, constants.MAX_UINT256),
|
|
makerAssetAmount: fromTokenUnitAmount(100, MAKER_DECIMALS),
|
|
takerAssetAmount: fromTokenUnitAmount(10, TAKER_DECIMALS),
|
|
makerAssetData: createBridgeAssetData(makerTokenAddress, BRIDGE_ADDRESS),
|
|
takerAssetData: createERC20AssetData(takerTokenAddress),
|
|
makerFeeAssetData: constants.NULL_BYTES,
|
|
takerFeeAssetData: constants.NULL_BYTES,
|
|
...orderFields,
|
|
};
|
|
}
|
|
|
|
describe('getDydxMakerBalance()', () => {
|
|
it('returns nonzero with valid order', async () => {
|
|
const order = createOrder();
|
|
const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
|
|
expect(r).to.not.bignumber.eq(0);
|
|
});
|
|
|
|
it('returns nonzero with valid order with an ERC721 taker asset', async () => {
|
|
const order = createOrder({
|
|
takerAssetData: createERC721AssetData(randomAddress(), getRandomInteger(1, constants.MAX_UINT256)),
|
|
});
|
|
const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
|
|
expect(r).to.not.bignumber.eq(0);
|
|
});
|
|
|
|
it('returns 0 if bridge is not a local operator', async () => {
|
|
const order = createOrder({
|
|
makerAssetData: createBridgeAssetData(ACCOUNT_OWNER, randomAddress()),
|
|
});
|
|
const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
|
|
expect(r).to.bignumber.eq(0);
|
|
});
|
|
|
|
it('returns 0 if bridge data does not have well-formed actions', async () => {
|
|
const order = createOrder({
|
|
makerAssetData: createBridgeAssetData(takerTokenAddress, BRIDGE_ADDRESS, {
|
|
// Two withdraw actions is invalid.
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(0),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0),
|
|
conversionRateDenominator: new BigNumber(0),
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
|
|
expect(r).to.bignumber.eq(0);
|
|
});
|
|
|
|
it('returns 0 if the maker token withdraw rate is < 1', async () => {
|
|
const order = createOrder({
|
|
makerAssetData: createBridgeAssetData(takerTokenAddress, BRIDGE_ADDRESS, {
|
|
actions: [
|
|
{
|
|
actionType: DydxBridgeActionType.Withdraw,
|
|
accountIdx: new BigNumber(0),
|
|
marketId: new BigNumber(1),
|
|
conversionRateNumerator: new BigNumber(0.99e18),
|
|
conversionRateDenominator: new BigNumber(1e18),
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
|
|
expect(r).to.bignumber.eq(0);
|
|
});
|
|
});
|
|
});
|
|
// tslint:disable-next-line: max-file-line-count
|