ethers#Transaction TypeScript Examples
The following examples show how to use
ethers#Transaction.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: main.ts From pawnft with GNU General Public License v3.0 | 4 votes |
describe("PawnBank", () => {
// Pre-setup
beforeEach(async () => {
// Reset hardhat forknet
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
blockNumber: 12864983,
},
},
],
});
// Deploy contract
await deploy();
// Scaffold initial loan
await scaffoldLoan();
});
describe("Loan creation", () => {
it("Should allow creating a loan", async () => {
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
const SquiggleOGOwner: string = (await PawnBankContract.pawnLoans(1)).tokenOwner;
// Verify that contract holds NFT
expect(SquiggleOwner).to.equal(PawnBankContractAddress);
// Verify that PawnLoan is created with correct owner
expect(SquiggleOGOwner).to.equal(ADDRESSES.SNOWFRO);
});
it("Should prevent creating a loan in the past", async () => {
const { SnowfroBank } = await impersonateBanks();
// Force delete scaffold loan
await SnowfroBank.cancelLoan(1);
// Approve NFT for transfer
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
await SquigglesContract.approve(PawnBankContractAddress, SQUIGGLE_0);
// Create loan w/ Chrome Squiggle #0 in past
const tx: Transaction = SnowfroBank.createLoan(
// Token address
ADDRESSES.SQUIGGLE,
// Token ID
SQUIGGLE_0,
// Interest rate
5,
// Max loan amount
ethers.utils.parseEther("10"),
// Loan completion time (5 years ago)
Math.floor(Date.now() / 1000) - 157680000
);
// Expect tx to revert because loan completion time < current time
await expect(tx).revertedWith(ERROR_MESSAGES.CREATE.NO_EXPIRED_LOAN);
});
});
describe("Loan underwriting", () => {
it("Should allow underwriting a new loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Collect details
const PawnBankBalance: BigNumber = await waffle.provider.getBalance(PawnBankContractAddress);
const PawnLoanDetails = await LenderOneBank.pawnLoans(1);
// Verify contract balance is increased
expect(PawnBankBalance.toString()).to.equal("1000000000000000000");
// Verify loan has 1 ether available to draw
expect(PawnLoanDetails.loanAmount.toString()).to.equal("1000000000000000000");
// Verify loan has 0 capital drawn
expect(PawnLoanDetails.loanAmountDrawn.toString()).to.equal("0");
// Verify new lender is Lender One
expect(PawnLoanDetails.lender).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should prevent underwriting a new loan with 0 Ether", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 0 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("0.0"),
});
// Expect tx to fail
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.NO_0_UNDERWRITE);
});
it("Should prevent underwriting a repaid loan", async () => {
const { SnowfroBank, LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Draw and repay loan
await SnowfroBank.drawLoan(1);
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });
// Attempt to re-underwrite loan
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.1"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.ALREADY_REPAID);
});
it("Should prevent underwriting an expired loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Back loan with 1 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.EXPIRED);
});
it("Should prevent underwriting a loan under the current top bid", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Back loan with <1 ETH
const tx: Transaction = LenderTwoBank.underwriteLoan(1, {
value: ethers.utils.parseEther("0.5"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.INSUFFICIENT_BID);
});
it("Should prevent underwriting a loan over the maxLoanAmount", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 11 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("11.0"),
});
// Expect tx to fail
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.OVER_MAX_UNDERWRITE);
});
it("Should allow underwriting a loan with existing bid", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// First lender balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
// Back loan with 5 ETH
const tx = await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Back loan with 6 ETH
const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 1);
const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
await LenderTwoBank.underwriteLoan(1, { value: higherLoan });
// Collect details
const NewTopLender: string = (await LenderTwoBank.pawnLoans(1)).lender;
const expectedLenderOneBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(totalInterest);
const acutalLenderOneBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Verify first lender received principle + interest
expect(acutalLenderOneBalance).to.be.gte(expectedLenderOneBalance);
// Verify second lender is now top bidder
expect(NewTopLender).to.equal(ADDRESSES.LENDER_TWO);
});
});
describe("Loan drawing", () => {
it("Should allow drawing from a loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Collect after Snowfro balance
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
expect(afterBalance).to.equal(expectedAfterBalance);
});
it("Should allow drawing additional capital from a new bid", async () => {
const { LenderOneBank, LenderTwoBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Check for Snowfro balance increment
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
expect(afterBalance).to.equal(expectedAfterBalance);
// Back loan with 6 ETH
const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 5);
const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
await LenderTwoBank.underwriteLoan(1, { value: higherLoan });
// Draw 6ETH
const tx2 = await SnowfroBank.drawLoan(1);
const receipt2 = await waffle.provider.getTransactionReceipt(tx2.hash);
// Check for Snowfro balance increment
const afterBalance2: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance2: BigNumber = afterBalance
.sub(tx2.gasPrice.mul(receipt2.cumulativeGasUsed))
.add(ethers.utils.parseEther("1.0"));
expect(afterBalance2).to.be.gte(expectedAfterBalance2);
});
it("Should prevent drawing from a loan with no bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Attempt to draw ETH
const tx: Transaction = SnowfroBank.drawLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
});
it("Should prevent non-owners from drawing from loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Attempt to collect loan as Lender
const tx: Transaction = LenderOneBank.drawLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.NOT_OWNER);
});
it("Should prevent consecutive draws from a loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Draw 5ETH
await SnowfroBank.drawLoan(1);
// Attempt to redraw
const tx: Transaction = SnowfroBank.drawLoan(1);
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
});
it("Should allow drawing loan after NFT seizure", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderOneBank.seizeNFT(1);
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Collect after Snowfro balance
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
// Expect increase in balance
expect(afterBalance).to.equal(expectedAfterBalance);
});
});
describe("Loan repayment", () => {
it("Should allow repaying loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Record Lender balance
const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await SnowfroBank.repayLoan(1, { value: repaymentBuffer });
// Expect loan to be closed
const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
// Expect NFT to be owned by original owner
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Expect increase in Lender One balance by ~.243 ETH
const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
});
it("Should allow repaying someone elses loan", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Record Lender balance
const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await LenderTwoBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await LenderTwoBank.repayLoan(1, { value: repaymentBuffer });
// Expect loan to be closed
const NewLoanOwner: string = (await LenderTwoBank.pawnLoans(1)).tokenOwner;
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
// Expect NFT to be owned by original owner
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Expect increase in Lender One balance by ~.243 ETH
const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
});
it("Should prevent repaying loan w/ 0 bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Attempt to repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.NO_BIDS);
});
it("Should prevent repaying expired loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Attempt to repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.EXPIRED);
});
it("Should prevent repaying paid loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await SnowfroBank.repayLoan(1, { value: repaymentBuffer });
// Attempt to re-repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.ALREADY_REPAID);
});
});
describe("Loan cancellation", () => {
it("Should allow cancelling loan with 0 bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Cancel loan
await SnowfroBank.cancelLoan(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;
// Verify that Snowfro holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Verify that PawnLoan is nullifed w/ 0 address
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
});
it("Should prevent cancelling loan with >0 bids", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Attempt to cancel loan with 1.0 bid existing
const tx: Transaction = SnowfroBank.cancelLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NON_ZERO_BIDS);
});
it("Should prevent cancelling loan if not owner", async () => {
const { LenderOneBank } = await impersonateBanks();
// Attempt to cancel loan as Lender One
const tx: Transaction = LenderOneBank.cancelLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NOT_OWNER);
});
});
describe("Loan seizing", () => {
it("Should allow lender to seize NFT", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderOneBank.seizeNFT(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
// Verify that Lender One holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should allow anyone to seize NFT on behalf of lender", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderTwoBank.seizeNFT(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
// Verify that Lender One holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should prevent seizing NFT before loan expiration", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Seize NFT
const tx: Transaction = LenderOneBank.seizeNFT(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.NOT_EXPIRED);
});
it("Should prevent seizing repaid loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Draw and repay loan
await SnowfroBank.drawLoan(1);
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });
// Seize NFT
const tx: Transaction = LenderOneBank.seizeNFT(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.ALREADY_REPAID);
});
});
});