"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAuthorizationResolver = exports.assertGroupDoesNotExist = exports.assertAuthorizationRequestValid = exports.UnreachableCaseError = exports.assertAddRelayerPayingKeyAuthorizationValid = exports.assertMultiSigSignerAuthorizationValid = exports.assertTransferAssetOwnershipAuthorizationValid = exports.assertTransferTickerAuthorizationValid = exports.assertAttestPrimaryKeyAuthorizationValid = exports.assertPrimaryKeyRotationAuthorizationValid = exports.assertRequirementsNotTooComplex = exports.isFullGroupType = exports.assertDistributionDatesValid = exports.assertCaCheckpointValid = exports.assertCaTaxWithholdingsValid = exports.assertCaTargetsValid = exports.assertDistributionOpen = exports.assertSecondaryAccounts = exports.assertVenueExists = exports.assertPortfolioExists = exports.assertInstructionValid = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = require("lodash");
const internal_1 = require("../../internal");
const types_1 = require("../../types");
const conversion_1 = require("../../utils/conversion");
const internal_2 = require("../../utils/internal");
/**
 * @hidden
 */
function assertInstructionValid(instruction, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const details = yield instruction.details();
        const { status, type } = details;
        if (status !== types_1.InstructionStatus.Pending) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Instruction must be in pending state',
            });
        }
        if (type === types_1.InstructionType.SettleOnBlock) {
            const latestBlock = yield context.getLatestBlock();
            const { endBlock } = details;
            if (latestBlock.gte(endBlock)) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.UnmetPrerequisite,
                    message: 'The Instruction cannot be modified; it has already reached its end block',
                    data: {
                        currentBlock: latestBlock,
                        endBlock,
                    },
                });
            }
        }
    });
}
exports.assertInstructionValid = assertInstructionValid;
/**
 * @hidden
 */
function assertPortfolioExists(portfolioId, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const { did, number } = portfolioId;
        if (number) {
            const numberedPortfolio = new internal_1.NumberedPortfolio({ did, id: number }, context);
            const exists = yield numberedPortfolio.exists();
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: "The Portfolio doesn't exist",
                    data: {
                        did,
                        portfolioId: number,
                    },
                });
            }
        }
    });
}
exports.assertPortfolioExists = assertPortfolioExists;
/**
 * @hidden
 */
function assertVenueExists(venueId, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const venue = new internal_1.Venue({ id: venueId }, context);
        const exists = yield venue.exists();
        if (!exists) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.DataUnavailable,
                message: "The Venue doesn't exist",
                data: {
                    venueId,
                },
            });
        }
    });
}
exports.assertVenueExists = assertVenueExists;
/**
 * @hidden
 */
function assertSecondaryAccounts(accounts, secondaryAccounts) {
    const notInTheList = [];
    accounts.forEach(account => {
        const isPresent = secondaryAccounts.find(({ account: existingAccount }) => account.isEqual(existingAccount));
        if (!isPresent) {
            notInTheList.push(account.address);
        }
    });
    if (notInTheList.length) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.UnmetPrerequisite,
            message: 'One of the Accounts is not a secondary Account for the Identity',
            data: {
                missing: notInTheList,
            },
        });
    }
}
exports.assertSecondaryAccounts = assertSecondaryAccounts;
/**
 * @hidden
 */
function assertDistributionOpen(paymentDate, expiryDate) {
    const now = new Date();
    if (paymentDate > now) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.UnmetPrerequisite,
            message: "The Distribution's payment date hasn't been reached",
            data: { paymentDate },
        });
    }
    if (expiryDate && expiryDate < now) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.UnmetPrerequisite,
            message: 'The Distribution has already expired',
            data: {
                expiryDate,
            },
        });
    }
}
exports.assertDistributionOpen = assertDistributionOpen;
/**
 * @hidden
 */
function assertCaTargetsValid(targets, context) {
    const { maxTargetIds } = context.polymeshApi.consts.corporateAction;
    const maxTargets = (0, conversion_1.u32ToBigNumber)(maxTargetIds);
    if (maxTargets.lt(targets.identities.length)) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.ValidationError,
            message: 'Too many target Identities',
            data: {
                maxTargets,
            },
        });
    }
}
exports.assertCaTargetsValid = assertCaTargetsValid;
/**
 * @hidden
 */
function assertCaTaxWithholdingsValid(taxWithholdings, context) {
    const { maxDidWhts } = context.polymeshApi.consts.corporateAction;
    const maxWithholdingEntries = (0, conversion_1.u32ToBigNumber)(maxDidWhts);
    if (maxWithholdingEntries.lt(taxWithholdings.length)) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.ValidationError,
            message: 'Too many tax withholding entries',
            data: {
                maxWithholdingEntries,
            },
        });
    }
}
exports.assertCaTaxWithholdingsValid = assertCaTaxWithholdingsValid;
/**
 * @hidden
 */
function assertCaCheckpointValid(checkpoint) {
    return __awaiter(this, void 0, void 0, function* () {
        if (checkpoint instanceof Date) {
            if (checkpoint <= new Date()) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.ValidationError,
                    message: 'Checkpoint date must be in the future',
                });
            }
        }
        else {
            const exists = yield checkpoint.exists();
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: checkpoint instanceof internal_1.Checkpoint
                        ? "Checkpoint doesn't exist"
                        : "Checkpoint Schedule doesn't exist",
                });
            }
        }
    });
}
exports.assertCaCheckpointValid = assertCaCheckpointValid;
/**
 * @hidden
 */
function assertDistributionDatesValid(checkpoint, paymentDate, expiryDate) {
    return __awaiter(this, void 0, void 0, function* () {
        let checkpointDate;
        if (checkpoint instanceof Date) {
            checkpointDate = checkpoint;
        }
        else {
            ({ nextCheckpointDate: checkpointDate } = yield checkpoint.details());
        }
        if (paymentDate <= checkpointDate) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: 'Payment date must be after the Checkpoint date',
            });
        }
        if (expiryDate && expiryDate < checkpointDate) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: 'Expiry date must be after the Checkpoint date',
            });
        }
    });
}
exports.assertDistributionDatesValid = assertDistributionDatesValid;
/**
 * @hidden
 */
function isFullGroupType(group) {
    return group instanceof internal_1.KnownPermissionGroup && group.type === types_1.PermissionGroupType.Full;
}
exports.isFullGroupType = isFullGroupType;
/**
 * @hidden
 *
 * @note based on the complexity calculation done by the chain
 * @note conditions have already been "injected" with the default trusted claim issuers when they reach this point
 */
function assertRequirementsNotTooComplex(conditions, defaultClaimIssuerLength, context) {
    const { maxConditionComplexity: maxComplexity } = context.polymeshApi.consts.complianceManager;
    let complexitySum = new bignumber_js_1.default(0);
    conditions.forEach(condition => {
        const { target, trustedClaimIssuers = [] } = condition;
        switch (condition.type) {
            case types_1.ConditionType.IsPresent:
            case types_1.ConditionType.IsIdentity:
            case types_1.ConditionType.IsAbsent:
                // single claim conditions add one to the complexity
                complexitySum = complexitySum.plus(1);
                break;
            case types_1.ConditionType.IsAnyOf:
            case types_1.ConditionType.IsNoneOf:
                // multi claim conditions add one to the complexity per each claim
                complexitySum = complexitySum.plus(condition.claims.length);
                break;
        }
        // if the condition affects both, it actually represents two conditions on chain
        if (target === types_1.ConditionTarget.Both) {
            complexitySum = complexitySum.multipliedBy(2);
        }
        const claimIssuerLength = trustedClaimIssuers.length || defaultClaimIssuerLength;
        complexitySum = complexitySum.multipliedBy(claimIssuerLength);
    });
    if ((0, conversion_1.u32ToBigNumber)(maxComplexity).lt(complexitySum)) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.LimitExceeded,
            message: 'Compliance Requirement complexity limit exceeded',
            data: { limit: maxComplexity },
        });
    }
}
exports.assertRequirementsNotTooComplex = assertRequirementsNotTooComplex;
/**
 * @hidden
 *
 * Asserts valid primary key rotation authorization
 */
function assertPrimaryKeyRotationAuthorizationValid(authRequest) {
    return __awaiter(this, void 0, void 0, function* () {
        if (authRequest.target instanceof internal_1.Identity) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: 'An Identity can not become the primary Account of another Identity',
            });
        }
    });
}
exports.assertPrimaryKeyRotationAuthorizationValid = assertPrimaryKeyRotationAuthorizationValid;
/**
 * @hidden
 *
 * Asserts valid attest primary key authorization
 */
function assertAttestPrimaryKeyAuthorizationValid(authRequest) {
    return __awaiter(this, void 0, void 0, function* () {
        const isCddProvider = yield authRequest.issuer.isCddProvider();
        if (!isCddProvider) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Issuer must be a CDD provider',
            });
        }
    });
}
exports.assertAttestPrimaryKeyAuthorizationValid = assertAttestPrimaryKeyAuthorizationValid;
/**
 * @hidden
 *
 * Asserts transfer ticker authorization is valid
 */
function assertTransferTickerAuthorizationValid(data, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const reservation = new internal_1.TickerReservation({ ticker: data.value }, context);
        const { status } = yield reservation.details();
        if (status === types_1.TickerReservationStatus.Free) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Ticker is not reserved',
            });
        }
        if (status === types_1.TickerReservationStatus.AssetCreated) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Ticker has already been used to create an Asset',
            });
        }
    });
}
exports.assertTransferTickerAuthorizationValid = assertTransferTickerAuthorizationValid;
/**
 * @hidden
 *
 * Asserts valid transfer asset ownership authorization
 */
function assertTransferAssetOwnershipAuthorizationValid(data, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const asset = new internal_1.Asset({ ticker: data.value }, context);
        const exists = yield asset.exists();
        if (!exists)
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Asset does not exist',
            });
    });
}
exports.assertTransferAssetOwnershipAuthorizationValid = assertTransferAssetOwnershipAuthorizationValid;
/**
 * @hidden
 *
 * Asserts valid add multisig signer authorization
 */
function assertMultiSigSignerAuthorizationValid(data, target, context) {
    return __awaiter(this, void 0, void 0, function* () {
        if (target instanceof internal_1.Account) {
            const { address } = target;
            if (address === data.value) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.ValidationError,
                    message: 'A multisig cannot be its own signer',
                });
            }
            const identityRecord = yield context.polymeshApi.query.identity.keyRecords(address);
            if (identityRecord.isSome) {
                const record = identityRecord.unwrap();
                if (record.isPrimaryKey || record.isSecondaryKey) {
                    throw new internal_1.PolymeshError({
                        code: types_1.ErrorCode.ValidationError,
                        message: 'The target Account is already part of an Identity',
                    });
                }
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.ValidationError,
                    message: 'The target Account is already associated to a multisig address',
                });
            }
        }
    });
}
exports.assertMultiSigSignerAuthorizationValid = assertMultiSigSignerAuthorizationValid;
/**
 * @hidden
 *
 * Asserts valid add relayer paying key authorization
 */
function assertAddRelayerPayingKeyAuthorizationValid(data) {
    return __awaiter(this, void 0, void 0, function* () {
        const subsidy = data.value;
        const [beneficiaryIdentity, subsidizerIdentity] = yield Promise.all([
            subsidy.beneficiary.getIdentity(),
            subsidy.subsidizer.getIdentity(),
        ]);
        if (!beneficiaryIdentity) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Beneficiary Account does not have an Identity',
            });
        }
        if (!subsidizerIdentity) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Subsidizer Account does not have an Identity',
            });
        }
        const [isBeneficiaryCddValid, isSubsidizerCddValid] = yield Promise.all([
            beneficiaryIdentity.hasValidCdd(),
            subsidizerIdentity.hasValidCdd(),
        ]);
        if (!isBeneficiaryCddValid) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Beneficiary Account does not have a valid CDD Claim',
            });
        }
        if (!isSubsidizerCddValid) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Subsidizer Account does not have a valid CDD Claim',
            });
        }
    });
}
exports.assertAddRelayerPayingKeyAuthorizationValid = assertAddRelayerPayingKeyAuthorizationValid;
/**
 * @hidden
 *
 * Assert the target is an Account
 */
function assertIsAccount(target) {
    if (target instanceof internal_1.Identity) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.UnmetPrerequisite,
            message: 'The target cannot be an Identity',
        });
    }
}
/**
 * @hidden
 *
 * Asserts valid authorization for JoinIdentity and RotatePrimaryKeyToSecondary types
 */
function assertJoinOrRotateAuthorizationValid(authRequest) {
    return __awaiter(this, void 0, void 0, function* () {
        const { issuer, target } = authRequest;
        const hasValidCdd = yield issuer.hasValidCdd();
        if (!hasValidCdd) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Issuing Identity does not have a valid CDD claim',
            });
        }
        assertIsAccount(target);
        const targetIdentity = yield target.getIdentity();
        if (targetIdentity) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The target Account already has an associated Identity',
            });
        }
    });
}
/**
 * @hidden
 *
 * Helper class to ensure a code path is unreachable. For example this can be used for ensuring switch statements are exhaustive
 */
class UnreachableCaseError extends Error {
    /** This should never be called */
    constructor(val) {
        super(`Unreachable case: ${JSON.stringify(val)}`);
    }
}
exports.UnreachableCaseError = UnreachableCaseError;
/**
 * @hidden
 */
function assertAuthorizationRequestValid(authRequest, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const exists = yield authRequest.exists();
        if (!exists) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Authorization Request no longer exists',
            });
        }
        if (authRequest.isExpired()) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The Authorization Request has expired',
                data: {
                    expiry: authRequest.expiry,
                },
            });
        }
        const { data, target } = authRequest;
        switch (data.type) {
            case types_1.AuthorizationType.RotatePrimaryKey:
                return assertPrimaryKeyRotationAuthorizationValid(authRequest);
            case types_1.AuthorizationType.AttestPrimaryKeyRotation:
                return assertAttestPrimaryKeyAuthorizationValid(authRequest);
            case types_1.AuthorizationType.TransferTicker:
                return assertTransferTickerAuthorizationValid(data, context);
            case types_1.AuthorizationType.TransferAssetOwnership:
                return assertTransferAssetOwnershipAuthorizationValid(data, context);
            case types_1.AuthorizationType.BecomeAgent:
                // no additional checks
                return;
            case types_1.AuthorizationType.AddMultiSigSigner:
                return assertMultiSigSignerAuthorizationValid(data, target, context);
            case types_1.AuthorizationType.PortfolioCustody:
                // no additional checks
                return;
            case types_1.AuthorizationType.JoinIdentity:
                return assertJoinOrRotateAuthorizationValid(authRequest);
            case types_1.AuthorizationType.AddRelayerPayingKey:
                return assertAddRelayerPayingKeyAuthorizationValid(data);
            case types_1.AuthorizationType.RotatePrimaryKeyToSecondary:
                return assertJoinOrRotateAuthorizationValid(authRequest);
            default:
                throw new UnreachableCaseError(data); // ensures switch statement covers all values
        }
    });
}
exports.assertAuthorizationRequestValid = assertAuthorizationRequestValid;
/**
 * @hidden
 */
function assertGroupDoesNotExist(asset, permissions) {
    return __awaiter(this, void 0, void 0, function* () {
        const { custom, known } = yield asset.permissions.getGroups();
        const allGroups = [...custom, ...known];
        const currentGroupPermissions = yield bluebird_1.default.map(allGroups, group => group.getPermissions());
        const duplicatedGroupIndex = currentGroupPermissions.findIndex(({ transactions: transactionPermissions }) => (0, lodash_1.isEqual)(transactionPermissions, permissions));
        if (duplicatedGroupIndex > -1) {
            const group = allGroups[duplicatedGroupIndex];
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.NoDataChange,
                message: 'There already exists a group with the exact same permissions',
                data: { groupId: group instanceof internal_1.CustomPermissionGroup ? group.id : group.type },
            });
        }
    });
}
exports.assertGroupDoesNotExist = assertGroupDoesNotExist;
const createAuthorizationResolver = (auth, issuer, target, expiry, context) => (receipt) => {
    const [{ data }] = (0, internal_2.filterEventRecords)(receipt, 'identity', 'AuthorizationAdded');
    let rawAuth;
    if (auth instanceof internal_1.PostTransactionValue) {
        rawAuth = auth.value;
    }
    else {
        rawAuth = auth;
    }
    const authId = (0, conversion_1.u64ToBigNumber)(data[3]);
    return new internal_1.AuthorizationRequest({ authId, expiry, issuer, target, data: rawAuth }, context);
};
exports.createAuthorizationResolver = createAuthorizationResolver;
//# sourceMappingURL=utils.js.map