Twenty new tests for MixinSignatureValidator. Fixed handling of unsupported types. Fixed trezor prefix.
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
import { BlockchainLifecycle } from '@0xproject/dev-utils';
|
||||
import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils';
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
import { addSignedMessagePrefix, assetProxyUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils';
|
||||
import { SignatureType, SignedOrder } from '@0xproject/types';
|
||||
import * as chai from 'chai';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TestSignatureValidatorContract } from '../../src/generated_contract_wrappers/test_signature_validator';
|
||||
import { TestValidatorContract } from '../../src/generated_contract_wrappers/test_validator';
|
||||
import { TestWalletContract } from '../../src/generated_contract_wrappers/test_wallet';
|
||||
import { addressUtils } from '../../src/utils/address_utils';
|
||||
import { artifacts } from '../../src/utils/artifacts';
|
||||
import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
|
||||
import { chaiSetup } from '../../src/utils/chai_setup';
|
||||
import { constants } from '../../src/utils/constants';
|
||||
import { OrderFactory } from '../../src/utils/order_factory';
|
||||
@@ -21,6 +25,12 @@ describe('MixinSignatureValidator', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let orderFactory: OrderFactory;
|
||||
let signatureValidator: TestSignatureValidatorContract;
|
||||
let testWallet: TestWalletContract;
|
||||
let testValidator: TestValidatorContract;
|
||||
let signerAddress: string;
|
||||
let signerPrivateKey: Buffer;
|
||||
let notSignerAddress: string;
|
||||
let notSignerPrivateKey: Buffer;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@@ -31,11 +41,31 @@ describe('MixinSignatureValidator', () => {
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const makerAddress = accounts[0];
|
||||
signerAddress = makerAddress;
|
||||
notSignerAddress = accounts[1];
|
||||
signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestSignatureValidator,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
testWallet = await TestWalletContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestWallet,
|
||||
provider,
|
||||
txDefaults,
|
||||
signerAddress,
|
||||
);
|
||||
testValidator = await TestValidatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestValidator,
|
||||
provider,
|
||||
txDefaults,
|
||||
signerAddress,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, {
|
||||
from: signerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
const defaultOrderParams = {
|
||||
...constants.STATIC_ORDER_PARAMS,
|
||||
@@ -45,8 +75,9 @@ describe('MixinSignatureValidator', () => {
|
||||
makerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
|
||||
takerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
|
||||
};
|
||||
const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
notSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(notSignerAddress)];
|
||||
orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -61,29 +92,369 @@ describe('MixinSignatureValidator', () => {
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
});
|
||||
|
||||
it('should return true with a valid signature', async () => {
|
||||
it('should revert with an empty signature', async () => {
|
||||
const emptySignature = '0x';
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
emptySignature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should revert with an unsupported signature type', async () => {
|
||||
const unsupportedSignatureType = SignatureType.NSignatureTypes;
|
||||
const unsupportedSignatureHex = `0x${unsupportedSignatureType}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
unsupportedSignatureHex,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should revert when SignatureType=Illegal', async () => {
|
||||
const unsupportedSignatureHex = `0x${SignatureType.Illegal}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
unsupportedSignatureHex,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
|
||||
const signatureHex = `0x${SignatureType.Invalid}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.signature,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should revert when SignatureType=Invalid and signature length is non-zero', async () => {
|
||||
const fillerData = ethUtil.toBuffer('0xdeadbeef');
|
||||
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Invalid}`);
|
||||
const signatureBuffer = Buffer.concat([fillerData, signatureType]);
|
||||
const signatureHex = ethUtil.bufferToHex(signatureBuffer);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
signatureHex,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=EIP712 and signature is valid', async () => {
|
||||
// Create EIP712 signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
|
||||
// Create 0x signature from EIP712 signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.EIP712}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return false with an invalid signature', async () => {
|
||||
const v = ethUtil.toBuffer(signedOrder.signature.slice(0, 4));
|
||||
const invalidR = ethUtil.sha3('invalidR');
|
||||
const invalidS = ethUtil.sha3('invalidS');
|
||||
const signatureType = ethUtil.toBuffer(`0x${signedOrder.signature.slice(-2)}`);
|
||||
const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]);
|
||||
const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
|
||||
signedOrder.signature = invalidSigHex;
|
||||
it('should return true when SignatureType=EIP712 and signature is valid', async () => {
|
||||
// Create EIP712 signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
|
||||
// Create 0x signature from EIP712 signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.EIP712}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
notSignerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=EthSign and signature is valid', async () => {
|
||||
// Create EthSign signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign);
|
||||
const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey);
|
||||
// Create 0x signature from EthSign signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.EthSign}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=EthSign and signature is invalid', async () => {
|
||||
// Create EthSign signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign);
|
||||
const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey);
|
||||
// Create 0x signature from EthSign signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.EthSign}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature.
|
||||
// This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress`
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
notSignerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Caller and signer is caller', async () => {
|
||||
const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
{ from: signerAddress },
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Caller and signer is not caller', async () => {
|
||||
const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
{ from: notSignerAddress },
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Wallet and signature is valid', async () => {
|
||||
// Create EIP712 signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
|
||||
// Create 0x signature from EIP712 signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.Wallet}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
testWallet.address,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Wallet and signature is invalid', async () => {
|
||||
// Create EIP712 signature using a private key that does not belong to the wallet owner.
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
|
||||
const notWalletOwnerPrivateKey = notSignerPrivateKey;
|
||||
const ecSignature = ethUtil.ecsign(orderHashBuffer, notWalletOwnerPrivateKey);
|
||||
// Create 0x signature from EIP712 signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.Wallet}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
testWallet.address,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => {
|
||||
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
|
||||
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
|
||||
const signature = Buffer.concat([validatorAddress, signatureType]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => {
|
||||
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
|
||||
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
|
||||
const signature = Buffer.concat([validatorAddress, signatureType]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
notSignerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => {
|
||||
// Set approval of signature validator to false
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(
|
||||
testValidator.address,
|
||||
false,
|
||||
{ from: signerAddress },
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// Validate signature
|
||||
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
|
||||
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
|
||||
const signature = Buffer.concat([validatorAddress, signatureType]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
// Set approval of signature validator back to true
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(
|
||||
testValidator.address,
|
||||
true,
|
||||
{ from: signerAddress },
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Trezor and signature is valid', async () => {
|
||||
// Create Trezor signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor);
|
||||
const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey);
|
||||
// Create 0x signature from Trezor signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.Trezor}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Trezor and signature is invalid', async () => {
|
||||
// Create Trezor signature
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor);
|
||||
const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex);
|
||||
const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey);
|
||||
// Create 0x signature from Trezor signature
|
||||
const signature = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ecSignature.r,
|
||||
ecSignature.s,
|
||||
ethUtil.toBuffer(`0x${SignatureType.Trezor}`),
|
||||
]);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
// Validate signature.
|
||||
// This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress`
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
notSignerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
it('should return true when SignatureType=Presigned and signer has presigned hash', async () => {
|
||||
// Presign hash
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await signatureValidator.preSign.sendTransactionAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.signature,
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// Validate presigned signature
|
||||
const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.true();
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Presigned has not presigned hash', async () => {
|
||||
const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`);
|
||||
const signatureHex = ethUtil.bufferToHex(signature);
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.signature,
|
||||
signatureHex,
|
||||
);
|
||||
expect(isValidSignature).to.be.false();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user