"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.getWithdrawableAmount = exports.getBalanceState = void 0;
const bn_js_1 = __importDefault(require("bn.js"));
const WORD_DATA_LENGTH = 64;
/**
 * Retrieves user's token balance as stored in the "balance" entry of the private exchange mapping balanceStates
 * Value is directly read from storage relying on Solidity's layout of storage variables
 * See https://solidity.readthedocs.io/en/develop/internals/layout_in_storage.html
 * @param userAddress - address of the user
 * @param tokenAddress - address of the token
 * @param batchExchangeAddress - address of the batch exchange
 * @param web3Provider - provider of Ethereum JavaScript API
 * @returns balance of the token for the given user as stored in balanceStates[userAddress][tokenAddress].balance
 */
function getBalanceState(userAddress, tokenAddress, batchExchangeAddress, web3Provider = web3) {
    return __awaiter(this, void 0, void 0, function* () {
        // TODO when Truffle depends on web3-utils version ^1.2.5:
        // use soliditySha3Raw instead of soliditySha3 and remove type coercion
        const BALANCESTATES_STORAGE_SLOT = "0x0";
        const userBalancestatesStorageSlot = web3Provider.utils.soliditySha3({
            type: "bytes32",
            value: web3Provider.utils.padLeft(userAddress, WORD_DATA_LENGTH),
        }, {
            type: "bytes32",
            value: web3Provider.utils.padLeft(BALANCESTATES_STORAGE_SLOT, WORD_DATA_LENGTH),
        });
        const targetStorageSlot = web3Provider.utils.soliditySha3({
            type: "bytes32",
            value: web3Provider.utils.padLeft(tokenAddress, WORD_DATA_LENGTH),
        }, {
            type: "bytes32",
            value: web3Provider.utils.padLeft(userBalancestatesStorageSlot, WORD_DATA_LENGTH),
        });
        const storageAtSlot = yield web3Provider.eth.getStorageAt(batchExchangeAddress, targetStorageSlot);
        return web3Provider.utils.toBN(storageAtSlot);
    });
}
exports.getBalanceState = getBalanceState;
/**
 * Computes amount of a token that a user can immediately withdraw from the exchange
 * It not only checks whether a withdrawal is pending, but also considers the balance available to the user,
 * pending deposits, and whether there were recent trades that would make withdrawing fail.
 * @param userAddress - address of the user
 * @param tokenAddress - address of the token
 * @param batchExchange - object representing the batch exchange smart contract
 * @param web3Provider - provider of Ethereum JavaScript API
 * @returns amount of token that the user would receive by calling batchExchange.withdraw
 */
function getWithdrawableAmount(userAddress, tokenAddress, batchExchange, web3Provider = web3) {
    return __awaiter(this, void 0, void 0, function* () {
        const [balanceState, pendingDeposit, pendingWithdrawal, lastCreditBatchId, batchId,] = yield Promise.all([
            getBalanceState(userAddress, tokenAddress, batchExchange.address, web3Provider),
            batchExchange.getPendingDeposit(userAddress, tokenAddress),
            batchExchange.getPendingWithdraw(userAddress, tokenAddress),
            batchExchange.lastCreditBatchId(userAddress, tokenAddress),
            batchExchange.getCurrentBatchId(),
        ]);
        let balance = balanceState;
        if (pendingDeposit[1].lt(batchId)) {
            balance = balance.add(pendingDeposit[0]);
        }
        if (pendingWithdrawal[1].gte(batchId) || lastCreditBatchId.gte(batchId)) {
            return new bn_js_1.default(0);
        }
        else {
            return bn_js_1.default.min(balance, pendingWithdrawal[0]);
        }
    });
}
exports.getWithdrawableAmount = getWithdrawableAmount;
