Copy import { expectEvent, expectRevert, time } from "@openzeppelin/test-helpers";
import { AgentSettings, CollateralType } from "../../../../lib/fasset/AssetManagerTypes";
import { PaymentReference } from "../../../../lib/fasset/PaymentReference";
import { AttestationHelper } from "../../../../lib/underlying-chain/AttestationHelper";
import { filterEvents, requiredEventArgs } from "../../../../lib/utils/events/truffle";
import { BNish, HOURS, MAX_BIPS, randomAddress, toBIPS, toBN, toBNExp, toNumber, toWei, ZERO_ADDRESS } from "../../../../lib/utils/helpers";
import { AgentVaultInstance, CollateralPoolInstance, ERC20MockInstance, FAssetInstance, IIAssetManagerInstance, WNatInstance } from "../../../../typechain-truffle";
import { TestChainInfo, testChainInfo } from "../../../integration/utils/TestChainInfo";
import { impersonateContract, stopImpersonatingContract } from "../../../utils/contract-test-helpers";
import { AssetManagerInitSettings, newAssetManager } from "../../../utils/fasset/CreateAssetManager";
import { MockChain, MockChainWallet } from "../../../utils/fasset/MockChain";
import { MockFlareDataConnectorClient } from "../../../utils/fasset/MockFlareDataConnectorClient";
import { deterministicTimeIncrease, getTestFile, loadFixtureCopyVars } from "../../../utils/test-helpers";
import { TestFtsos, TestSettingsContracts, createFtsoMock, createTestAgent, createTestCollaterals, createTestContracts, createTestFtsos, createTestSettings } from "../../../utils/test-settings";
import { assertWeb3Equal } from "../../../utils/web3assertions";
import {ethers} from "ethers";
const CollateralPool = artifacts.require("CollateralPool");
contract(`Redemption.sol; ${getTestFile(__filename)}; Redemption basic tests`, async accounts => {
const governance = accounts[10];
let assetManagerController = accounts[11];
let contracts: TestSettingsContracts;
let assetManager: IIAssetManagerInstance;
let fAsset: FAssetInstance;
let wNat: WNatInstance;
let usdc: ERC20MockInstance;
let ftsos: TestFtsos;
let settings: AssetManagerInitSettings;
let collaterals: CollateralType[];
let chain: MockChain;
let chainInfo: TestChainInfo;
let wallet: MockChainWallet;
let flareDataConnectorClient: MockFlareDataConnectorClient;
let attestationProvider: AttestationHelper;
let collateralPool: CollateralPoolInstance;
// addresses
const agentOwner1 = accounts[20];
const agentOwner2 = accounts[21];
const underlyingAgent1 = "Agent1"; // addresses on mock underlying chain can be any string, as long as it is unique
const underlyingAgent2 = "Agent2";
const minterAddress1 = accounts[30];
const redeemerAddress1 = accounts[40];
const redeemerAddress2 = accounts[41];
const executorAddress1 = accounts[45];
const executorAddress2 = accounts[46];
const underlyingMinter1 = "Minter1";
const underlyingRedeemer1 = "Redeemer1";
const underlyingRedeemer2 = "Redeemer2";
const executorFee = toWei(0.1);
function createAgent(owner: string, underlyingAddress: string, options?: Partial<AgentSettings>) {
const vaultCollateralToken = options?.vaultCollateralToken ?? usdc.address;
return createTestAgent({ assetManager, settings, chain, wallet, attestationProvider }, owner, underlyingAddress, vaultCollateralToken, options);
}
async function depositAndMakeAgentAvailable(agentVault: AgentVaultInstance, owner: string, fullAgentCollateral: BN = toWei(3e8)) {
await depositCollateral(owner, agentVault, fullAgentCollateral);
await agentVault.buyCollateralPoolTokens({ from: owner, value: fullAgentCollateral }); // add pool collateral and agent pool tokens
await assetManager.makeAgentAvailable(agentVault.address, { from: owner });
}
async function depositCollateral(owner: string, agentVault: AgentVaultInstance, amount: BN, token: ERC20MockInstance = usdc) {
await token.mintAmount(owner, amount);
await token.approve(agentVault.address, amount, { from: owner });
await agentVault.depositCollateral(token.address, amount, { from: owner });
}
async function updateUnderlyingBlock() {
const proof = await attestationProvider.proveConfirmedBlockHeightExists(Number(settings.attestationWindowSeconds));
await assetManager.updateCurrentBlock(proof);
return toNumber(proof.data.requestBody.blockNumber) + toNumber(proof.data.responseBody.numberOfConfirmations);
}
async function mintAndRedeem(agentVault: AgentVaultInstance, chain: MockChain, underlyingMinterAddress: string, minterAddress: string, underlyingRedeemerAddress: string, redeemerAddress: string, updateBlock: boolean, approveCollateralReservation: boolean = false, agentOwner: string = agentOwner1) {
// minter
chain.mint(underlyingMinterAddress, toBNExp(10000, 18));
if (updateBlock) await updateUnderlyingBlock();
// perform minting
const lots = 3;
const agentInfo = await assetManager.getAgentInfo(agentVault.address);
const crFee = await assetManager.collateralReservationFee(lots);
const resAg = await assetManager.reserveCollateral(agentVault.address, lots, agentInfo.feeBIPS, ZERO_ADDRESS, [underlyingMinterAddress], { from: minterAddress, value: crFee });
let crt;
if (approveCollateralReservation) {
const args = requiredEventArgs(resAg, 'HandshakeRequired');
// approve reservation
const tx1 = await assetManager.approveCollateralReservation(args.collateralReservationId, { from: agentOwner });
crt = requiredEventArgs(tx1, 'CollateralReserved');
} else {
crt = requiredEventArgs(resAg, 'CollateralReserved');
}
const paymentAmount = crt.valueUBA.add(crt.feeUBA);
const txHash = await wallet.addTransaction(underlyingMinterAddress, crt.paymentAddress, paymentAmount, crt.paymentReference);
const proof = await attestationProvider.provePayment(txHash, underlyingMinterAddress, crt.paymentAddress);
const res = await assetManager.executeMinting(proof, crt.collateralReservationId, { from: minterAddress });
const minted = requiredEventArgs(res, 'MintingExecuted');
// redeemer "buys" f-assets
await fAsset.transfer(redeemerAddress, minted.mintedAmountUBA, { from: minterAddress });
// redemption request
const assetManagerBalance = await web3.eth.getBalance(assetManager.address);
console.log("Balance asset manager before redeem : ", assetManagerBalance);
const resR = await assetManager.redeem(lots, underlyingRedeemerAddress, executorAddress1, { from: redeemerAddress, value: executorFee });
const redemptionRequests = filterEvents(resR, 'RedemptionRequested').map(e => e.args);
const request = redemptionRequests[0];
const assetManagerBalanceAfter = await web3.eth.getBalance(assetManager.address);
console.log("Balance asset manager after redeem : ", ethers.utils.formatEther(assetManagerBalanceAfter));
return request;
}
async function initialize() {
const ci = chainInfo = testChainInfo.eth;
contracts = await createTestContracts(governance);
// save some contracts as globals
({ wNat } = contracts);
usdc = contracts.stablecoins.USDC;
// create FTSOs for nat, stablecoins and asset and set some price
ftsos = await createTestFtsos(contracts.ftsoRegistry, ci);
// create mock chain and attestation provider
chain = new MockChain(await time.latest());
wallet = new MockChainWallet(chain);
flareDataConnectorClient = new MockFlareDataConnectorClient(contracts.fdcHub, contracts.relay, { [ci.chainId]: chain }, 'auto');
attestationProvider = new AttestationHelper(flareDataConnectorClient, chain, ci.chainId);
// create asset manager
collaterals = createTestCollaterals(contracts, ci);
settings = createTestSettings(contracts, ci, { requireEOAAddressProof: true });
[assetManager, fAsset] = await newAssetManager(governance, assetManagerController, ci.name, ci.symbol, ci.decimals, settings, collaterals, ci.assetName, ci.assetSymbol);
return { contracts, wNat, usdc, ftsos, chain, wallet, flareDataConnectorClient, attestationProvider, collaterals, settings, assetManager, fAsset };
}
beforeEach(async () => {
({ contracts, wNat, usdc, ftsos, chain, wallet, flareDataConnectorClient, attestationProvider, collaterals, settings, assetManager, fAsset } = await loadFixtureCopyVars(initialize));
});
it("mint and redeem with address validation - invalid address", async () => {
const assetManagerStartbalance = await web3.eth.getBalance(assetManager.address);
console.log("Start balance asset manager : ", assetManagerStartbalance);
const agentVault = await createAgent(agentOwner1, underlyingAgent1);
await depositAndMakeAgentAvailable(agentVault, agentOwner1);
const agentInfo1 = await assetManager.getAgentInfo(agentVault.address);
const request = await mintAndRedeem(agentVault, chain, underlyingMinter1, minterAddress1, "MY_INVALID_ADDRESS", redeemerAddress1, true);
const agentInfo2 = await assetManager.getAgentInfo(agentVault.address);
assert.equal(Number(agentInfo2.freeCollateralLots), Number(agentInfo1.freeCollateralLots) - 2);
const proof = await attestationProvider.proveAddressValidity(request.paymentAddress);
assert.isFalse(proof.data.responseBody.isValid);
const res = await assetManager.rejectInvalidRedemption(proof, request.requestId, { from: agentOwner1 });
expectEvent(res, 'RedemptionRejected', { requestId: request.requestId, redemptionAmountUBA: request.valueUBA });
const agentInfo3 = await assetManager.getAgentInfo(agentVault.address);
assert.equal(Number(agentInfo3.freeCollateralLots), Number(agentInfo1.freeCollateralLots));
const assetManagerEndbalance = await web3.eth.getBalance(assetManager.address);
console.log("End balance asset manager : ", ethers.utils.formatEther(assetManagerEndbalance));
});
});