flashLoan rewritten as ERC-3156
This commit is contained in:
137
test/GasTest.sol
137
test/GasTest.sol
@@ -8,104 +8,74 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import "../src/PartyPlanner.sol";
|
||||
import "../src/IPartyFlashCallback.sol";
|
||||
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {Deploy} from "../src/Deploy.sol";
|
||||
|
||||
/// @notice Test contract that implements the flash callback for testing flash loans
|
||||
contract FlashBorrower is IPartyFlashCallback {
|
||||
contract FlashBorrower is IERC3156FlashBorrower {
|
||||
enum Action {
|
||||
NORMAL, // Normal repayment
|
||||
REPAY_NONE, // Don't repay anything
|
||||
REPAY_PARTIAL, // Repay less than required
|
||||
REPAY_NO_FEE, // Repay only the principal without fee
|
||||
REPAY_EXACT, // Repay exactly the required amount
|
||||
REPAY_EXTRA // Repay more than required (donation)
|
||||
REPAY_EXACT // Repay exactly the required amount
|
||||
}
|
||||
|
||||
Action public action;
|
||||
address public pool;
|
||||
address public recipient;
|
||||
address[] public tokens;
|
||||
address public payer;
|
||||
|
||||
constructor(address _pool, IERC20[] memory _tokens) {
|
||||
constructor(address _pool) {
|
||||
pool = _pool;
|
||||
tokens = new address[](_tokens.length);
|
||||
for (uint i = 0; i < _tokens.length; i++) {
|
||||
tokens[i] = address(_tokens[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function setAction(Action _action, address _recipient) external {
|
||||
function setAction(Action _action, address _payer) external {
|
||||
action = _action;
|
||||
recipient = _recipient;
|
||||
payer = _payer;
|
||||
}
|
||||
|
||||
function flash(uint256[] memory amounts) external {
|
||||
PartyPool(pool).flash(recipient, amounts, "");
|
||||
function flash(address token, uint256 amount) external {
|
||||
PartyPool(pool).flashLoan(IERC3156FlashBorrower(address(this)), token, amount, "");
|
||||
}
|
||||
|
||||
function partyFlashCallback(
|
||||
uint256[] memory loanAmounts,
|
||||
uint256[] memory repaymentAmounts,
|
||||
function onFlashLoan(
|
||||
address initiator,
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint256 fee,
|
||||
bytes calldata /* data */
|
||||
) external override {
|
||||
) external override returns (bytes32) {
|
||||
require(msg.sender == pool, "Callback not called by pool");
|
||||
|
||||
if (action == Action.NORMAL || action == Action.REPAY_EXTRA) {
|
||||
// Normal or extra repayment - transfer required amounts back to pool
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
uint256 repaymentAmount = repaymentAmounts[i];
|
||||
if (action == Action.NORMAL) {
|
||||
// Normal repayment
|
||||
// We received 'amount' from the pool, need to pay back amount + fee
|
||||
uint256 repaymentAmount = amount + fee;
|
||||
|
||||
// For REPAY_EXTRA, add 1 to each repayment
|
||||
if (action == Action.REPAY_EXTRA) {
|
||||
repaymentAmount += 1;
|
||||
}
|
||||
// Transfer the fee from payer to this contract
|
||||
// (we already have the principal 'amount' from the flash loan)
|
||||
TestERC20(token).transferFrom(payer, address(this), fee);
|
||||
|
||||
// Transfer from recipient back to pool
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
repaymentAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
// Approve pool to pull back the full repayment
|
||||
TestERC20(token).approve(pool, repaymentAmount);
|
||||
} else if (action == Action.REPAY_PARTIAL) {
|
||||
// Repay half of the required amounts
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
uint256 partialRepayment = repaymentAmounts[i] / 2;
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
partialRepayment
|
||||
);
|
||||
}
|
||||
}
|
||||
// Repay half of the required amount
|
||||
uint256 partialRepayment = (amount + fee) / 2;
|
||||
TestERC20(token).approve(pool, partialRepayment);
|
||||
} else if (action == Action.REPAY_NO_FEE) {
|
||||
// Repay only the principal without fee
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
loanAmounts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
// Repay only the principal without fee (we already have it from the loan)
|
||||
TestERC20(token).approve(pool, amount);
|
||||
} else if (action == Action.REPAY_EXACT) {
|
||||
// Repay exactly what was required
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
repaymentAmounts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
uint256 repaymentAmount = amount + fee;
|
||||
// Transfer the fee from payer (we have the principal from the loan)
|
||||
TestERC20(token).transferFrom(payer, address(this), fee);
|
||||
// Approve pool to pull back the full repayment
|
||||
TestERC20(token).approve(pool, repaymentAmount);
|
||||
}
|
||||
// For REPAY_NONE, do nothing (don't repay)
|
||||
// For REPAY_NONE, do nothing (don't approve repayment)
|
||||
|
||||
return keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,13 +217,11 @@ contract GasTest is Test {
|
||||
|
||||
/// @notice Setup a flash borrower for testing
|
||||
function setupFlashBorrower() internal returns (FlashBorrower borrower) {
|
||||
// Get token addresses from the 2-token pool
|
||||
IERC20[] memory tokenAddresses = pool2.allTokens();
|
||||
|
||||
// Deploy the borrower contract
|
||||
borrower = new FlashBorrower(address(pool2), tokenAddresses);
|
||||
borrower = new FlashBorrower(address(pool2));
|
||||
|
||||
// Mint tokens to alice to be used for repayments and approve borrower
|
||||
IERC20[] memory tokenAddresses = pool2.allTokens();
|
||||
vm.startPrank(alice);
|
||||
for (uint256 i = 0; i < tokenAddresses.length; i++) {
|
||||
TestERC20(address(tokenAddresses[i])).mint(alice, INIT_BAL * 2);
|
||||
@@ -441,33 +409,14 @@ contract GasTest is Test {
|
||||
// Configure borrower
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request for single token (get array size from pool)
|
||||
// Get first token from pool
|
||||
IERC20[] memory poolTokens = pool2.allTokens();
|
||||
uint256[] memory amounts = new uint256[](poolTokens.length);
|
||||
amounts[0] = 1000;
|
||||
address token = address(poolTokens[0]);
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Execute flash loan 10 times to measure gas
|
||||
for (uint256 i = 0; i < 10; i++) {
|
||||
borrower.flash(amounts);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Gas measurement: flash with multiple tokens
|
||||
function testFlashGasMultipleTokens() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
|
||||
// Configure borrower
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request for multiple tokens (get array size from pool)
|
||||
IERC20[] memory poolTokens = pool2.allTokens();
|
||||
uint256[] memory amounts = new uint256[](poolTokens.length);
|
||||
amounts[0] = 1000;
|
||||
amounts[1] = 2000;
|
||||
|
||||
// Execute flash loan 10 times to measure gas
|
||||
for (uint256 i = 0; i < 10; i++) {
|
||||
borrower.flash(amounts);
|
||||
borrower.flash(token, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,103 +9,76 @@ import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
|
||||
// Import the flash callback interface
|
||||
import "../src/IPartyFlashCallback.sol";
|
||||
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {Deploy} from "../src/Deploy.sol";
|
||||
import {PartyPoolView} from "../src/PartyPoolView.sol";
|
||||
|
||||
/// @notice Test contract that implements the flash callback for testing flash loans
|
||||
contract FlashBorrower is IPartyFlashCallback {
|
||||
contract FlashBorrower is IERC3156FlashBorrower {
|
||||
enum Action {
|
||||
NORMAL, // Normal repayment
|
||||
REPAY_NONE, // Don't repay anything
|
||||
REPAY_PARTIAL, // Repay less than required
|
||||
REPAY_NO_FEE, // Repay only the principal without fee
|
||||
REPAY_EXACT, // Repay exactly the required amount
|
||||
REPAY_EXTRA // Repay more than required (donation)
|
||||
REPAY_EXACT // Repay exactly the required amount
|
||||
}
|
||||
|
||||
Action public action;
|
||||
address public pool;
|
||||
address public recipient;
|
||||
address[] public tokens;
|
||||
address public payer;
|
||||
|
||||
constructor(address _pool, address[] memory _tokens) {
|
||||
constructor(address _pool) {
|
||||
pool = _pool;
|
||||
tokens = _tokens;
|
||||
}
|
||||
|
||||
function setAction(Action _action, address _recipient) external {
|
||||
function setAction(Action _action, address _payer) external {
|
||||
action = _action;
|
||||
recipient = _recipient;
|
||||
payer = _payer;
|
||||
}
|
||||
|
||||
function flash(uint256[] memory amounts) external {
|
||||
PartyPool(pool).flash(recipient, amounts, "");
|
||||
function flash(address token, uint256 amount) external {
|
||||
PartyPool(pool).flashLoan(IERC3156FlashBorrower(address(this)), token, amount, "");
|
||||
}
|
||||
|
||||
function partyFlashCallback(
|
||||
uint256[] memory loanAmounts,
|
||||
uint256[] memory repaymentAmounts,
|
||||
function onFlashLoan(
|
||||
address initiator,
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint256 fee,
|
||||
bytes calldata /* data */
|
||||
) external override {
|
||||
) external override returns (bytes32) {
|
||||
require(msg.sender == pool, "Callback not called by pool");
|
||||
|
||||
if (action == Action.NORMAL || action == Action.REPAY_EXTRA) {
|
||||
// Normal or extra repayment - transfer required amounts back to pool
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
uint256 repaymentAmount = repaymentAmounts[i];
|
||||
if (action == Action.NORMAL) {
|
||||
// Normal repayment
|
||||
// We received 'amount' from the pool, need to pay back amount + fee
|
||||
uint256 repaymentAmount = amount + fee;
|
||||
|
||||
// For REPAY_EXTRA, add 1 to each repayment
|
||||
if (action == Action.REPAY_EXTRA) {
|
||||
repaymentAmount += 1;
|
||||
}
|
||||
// Transfer the fee from payer to this contract
|
||||
// (we already have the principal 'amount' from the flash loan)
|
||||
TestERC20(token).transferFrom(payer, address(this), fee);
|
||||
|
||||
// Transfer from recipient back to pool
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
repaymentAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
// Approve pool to pull back the full repayment
|
||||
TestERC20(token).approve(pool, repaymentAmount);
|
||||
} else if (action == Action.REPAY_PARTIAL) {
|
||||
// Repay half of the required amounts
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
uint256 partialRepayment = repaymentAmounts[i] / 2;
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
partialRepayment
|
||||
);
|
||||
}
|
||||
}
|
||||
// Repay half of the required amount
|
||||
uint256 partialRepayment = (amount + fee) / 2;
|
||||
TestERC20(token).approve(pool, partialRepayment);
|
||||
} else if (action == Action.REPAY_NO_FEE) {
|
||||
// Repay only the principal without fee
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
loanAmounts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
// Repay only the principal without fee (we already have it from the loan)
|
||||
TestERC20(token).approve(pool, amount);
|
||||
} else if (action == Action.REPAY_EXACT) {
|
||||
// Repay exactly what was required
|
||||
for (uint256 i = 0; i < loanAmounts.length; i++) {
|
||||
if (loanAmounts[i] > 0) {
|
||||
TestERC20(tokens[i]).transferFrom(
|
||||
recipient,
|
||||
pool,
|
||||
repaymentAmounts[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
uint256 repaymentAmount = amount + fee;
|
||||
// Transfer the fee from payer (we have the principal from the loan)
|
||||
TestERC20(token).transferFrom(payer, address(this), fee);
|
||||
// Approve pool to pull back the full repayment
|
||||
TestERC20(token).approve(pool, repaymentAmount);
|
||||
}
|
||||
// For REPAY_NONE, do nothing (don't repay)
|
||||
// For REPAY_NONE, do nothing (don't approve repayment)
|
||||
|
||||
return keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,14 +819,8 @@ contract PartyPoolTest is Test {
|
||||
|
||||
/// @notice Setup a flash borrower for testing
|
||||
function setupFlashBorrower() internal returns (FlashBorrower borrower) {
|
||||
// Create array of token addresses for borrower
|
||||
address[] memory tokenAddresses = new address[](3);
|
||||
tokenAddresses[0] = address(token0);
|
||||
tokenAddresses[1] = address(token1);
|
||||
tokenAddresses[2] = address(token2);
|
||||
|
||||
// Deploy the borrower contract
|
||||
borrower = new FlashBorrower(address(pool), tokenAddresses);
|
||||
borrower = new FlashBorrower(address(pool));
|
||||
|
||||
// Mint tokens to alice to be used for repayments
|
||||
token0.mint(alice, INIT_BAL * 2);
|
||||
@@ -876,18 +843,17 @@ contract PartyPoolTest is Test {
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request for token0 only
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000; // Only borrow token0
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Record balances before flash
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
uint256 poolToken0Before = token0.balanceOf(address(pool));
|
||||
|
||||
// Execute flash loan
|
||||
borrower.flash(amounts);
|
||||
borrower.flash(address(token0), amount);
|
||||
|
||||
// Net change for alice should equal the flash fee (principal is returned during repayment)
|
||||
uint256 fee = (amounts[0] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 fee = (amount * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 expectedAliceDecrease = fee;
|
||||
assertEq(
|
||||
aliceToken0Before - token0.balanceOf(alice),
|
||||
@@ -903,126 +869,6 @@ contract PartyPoolTest is Test {
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with multiple tokens
|
||||
function testFlashLoanMultipleTokens() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
|
||||
// Configure borrower to repay normally
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request for all tokens
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
amounts[1] = 2000;
|
||||
amounts[2] = 3000;
|
||||
|
||||
// Record balances before flash
|
||||
uint256[] memory aliceBalancesBefore = new uint256[](3);
|
||||
uint256[] memory poolBalancesBefore = new uint256[](3);
|
||||
|
||||
aliceBalancesBefore[0] = token0.balanceOf(alice);
|
||||
aliceBalancesBefore[1] = token1.balanceOf(alice);
|
||||
aliceBalancesBefore[2] = token2.balanceOf(alice);
|
||||
|
||||
poolBalancesBefore[0] = token0.balanceOf(address(pool));
|
||||
poolBalancesBefore[1] = token1.balanceOf(address(pool));
|
||||
poolBalancesBefore[2] = token2.balanceOf(address(pool));
|
||||
|
||||
// Execute flash loan
|
||||
borrower.flash(amounts);
|
||||
|
||||
// Check balances for each token
|
||||
for (uint256 i = 0; i < 3; i++) {
|
||||
uint256 fee = (amounts[i] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 expectedAliceDecrease = fee;
|
||||
|
||||
IERC20 token;
|
||||
if (i == 0) token = token0;
|
||||
else if (i == 1) token = token1;
|
||||
else token = token2;
|
||||
|
||||
// Net change for Alice should equal the flash fee for this token (principal was returned)
|
||||
assertEq(
|
||||
aliceBalancesBefore[i] - token.balanceOf(alice),
|
||||
expectedAliceDecrease,
|
||||
"Alice should pay flash fee for token"
|
||||
);
|
||||
|
||||
// Pool's balance increased by fee
|
||||
assertEq(
|
||||
token.balanceOf(address(pool)),
|
||||
poolBalancesBefore[i] + fee,
|
||||
"Pool should receive fee for token"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with some zero amounts (should be skipped)
|
||||
function testFlashLoanWithZeroAmounts() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
|
||||
// Configure borrower to repay normally
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request with mix of zero and non-zero amounts
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 0; // Zero - should be skipped
|
||||
amounts[1] = 2000; // Non-zero
|
||||
amounts[2] = 0; // Zero - should be skipped
|
||||
|
||||
// Record balances before flash
|
||||
uint256 aliceToken1Before = token1.balanceOf(alice);
|
||||
uint256 poolToken1Before = token1.balanceOf(address(pool));
|
||||
|
||||
// Tokens that should remain unchanged
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
uint256 aliceToken2Before = token2.balanceOf(alice);
|
||||
uint256 poolToken0Before = token0.balanceOf(address(pool));
|
||||
uint256 poolToken2Before = token2.balanceOf(address(pool));
|
||||
|
||||
// Execute flash loan
|
||||
borrower.flash(amounts);
|
||||
|
||||
// Check token1 balances changed appropriately
|
||||
uint256 fee = (amounts[1] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 expectedAliceDecrease = fee;
|
||||
|
||||
assertEq(
|
||||
aliceToken1Before - token1.balanceOf(alice),
|
||||
expectedAliceDecrease,
|
||||
"Alice should pay flash fee for token1"
|
||||
);
|
||||
|
||||
assertEq(
|
||||
token1.balanceOf(address(pool)),
|
||||
poolToken1Before + fee,
|
||||
"Pool should receive fee for token1"
|
||||
);
|
||||
|
||||
// Check token0 and token2 balances remained unchanged
|
||||
assertEq(token0.balanceOf(alice), aliceToken0Before, "Alice token0 balance should be unchanged");
|
||||
assertEq(token2.balanceOf(alice), aliceToken2Before, "Alice token2 balance should be unchanged");
|
||||
assertEq(token0.balanceOf(address(pool)), poolToken0Before, "Pool token0 balance should be unchanged");
|
||||
assertEq(token2.balanceOf(address(pool)), poolToken2Before, "Pool token2 balance should be unchanged");
|
||||
}
|
||||
|
||||
/// @notice Test that flash reverts when all amounts are zero
|
||||
function testFlashLoanAllZeroAmountsReverts() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
|
||||
// Configure borrower to repay normally
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
|
||||
|
||||
// Create loan request with all zeros
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 0;
|
||||
amounts[1] = 0;
|
||||
amounts[2] = 0;
|
||||
|
||||
// Execute flash loan - should revert
|
||||
vm.expectRevert(bytes("flash: no tokens requested"));
|
||||
borrower.flash(amounts);
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with incorrect repayment (none)
|
||||
function testFlashLoanNoRepaymentReverts() public {
|
||||
@@ -1032,12 +878,11 @@ contract PartyPoolTest is Test {
|
||||
borrower.setAction(FlashBorrower.Action.REPAY_NONE, alice);
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Execute flash loan - should revert on validation
|
||||
vm.expectRevert(bytes("flash: repayment failed"));
|
||||
borrower.flash(amounts);
|
||||
// Execute flash loan - should revert due to insufficient allowance when pool tries to pull repayment
|
||||
vm.expectRevert();
|
||||
borrower.flash(address(token0), amount);
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with partial repayment (should revert)
|
||||
@@ -1048,12 +893,11 @@ contract PartyPoolTest is Test {
|
||||
borrower.setAction(FlashBorrower.Action.REPAY_PARTIAL, alice);
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Execute flash loan - should revert on validation
|
||||
vm.expectRevert(bytes("flash: repayment failed"));
|
||||
borrower.flash(amounts);
|
||||
// Execute flash loan - should revert due to insufficient allowance when pool tries to pull full repayment
|
||||
vm.expectRevert();
|
||||
borrower.flash(address(token0), amount);
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with principal repayment but no fee (should revert)
|
||||
@@ -1064,16 +908,15 @@ contract PartyPoolTest is Test {
|
||||
borrower.setAction(FlashBorrower.Action.REPAY_NO_FEE, alice);
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Execute flash loan - should revert on validation if fee > 0
|
||||
// Execute flash loan - should revert due to insufficient allowance if fee > 0
|
||||
if (pool.flashFeePpm() > 0) {
|
||||
vm.expectRevert(bytes("flash: repayment failed"));
|
||||
borrower.flash(amounts);
|
||||
vm.expectRevert();
|
||||
borrower.flash(address(token0), amount);
|
||||
} else {
|
||||
// If fee is zero, this should succeed
|
||||
borrower.flash(amounts);
|
||||
borrower.flash(address(token0), amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,18 +928,17 @@ contract PartyPoolTest is Test {
|
||||
borrower.setAction(FlashBorrower.Action.REPAY_EXACT, alice);
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
uint256 amount = 1000;
|
||||
|
||||
// Record balances before flash
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
uint256 poolToken0Before = token0.balanceOf(address(pool));
|
||||
|
||||
// Execute flash loan
|
||||
borrower.flash(amounts);
|
||||
borrower.flash(address(token0), amount);
|
||||
|
||||
// Check balances: net change for alice should equal the fee
|
||||
uint256 fee = (amounts[0] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 fee = (amount * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 expectedAliceDecrease = fee;
|
||||
|
||||
assertEq(
|
||||
@@ -1112,115 +954,29 @@ contract PartyPoolTest is Test {
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Test flash loan with extra repayment (donation, should succeed)
|
||||
function testFlashLoanExtraRepayment() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
/// @notice Test flashFee view function matches flash implementation
|
||||
function testFlashFee() public view {
|
||||
// Test different loan amounts
|
||||
uint256[] memory testAmounts = new uint256[](3);
|
||||
testAmounts[0] = 1000;
|
||||
testAmounts[1] = 2000;
|
||||
testAmounts[2] = 3000;
|
||||
|
||||
// Configure borrower to repay more than required
|
||||
borrower.setAction(FlashBorrower.Action.REPAY_EXTRA, alice);
|
||||
for (uint256 i = 0; i < testAmounts.length; i++) {
|
||||
uint256 amount = testAmounts[i];
|
||||
uint256 fee = viewer.flashFee(pool, address(token0), amount);
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
// Calculate expected fee
|
||||
uint256 expectedFee = (amount * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceiling
|
||||
|
||||
// Record balances before flash
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
uint256 poolToken0Before = token0.balanceOf(address(pool));
|
||||
|
||||
// Execute flash loan
|
||||
borrower.flash(amounts);
|
||||
|
||||
// Check balances - net change for alice should equal fee + extra donation (principal returned)
|
||||
uint256 fee = (amounts[0] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceil fee calculation
|
||||
uint256 extra = 1; // borrower donates +1 per token in REPAY_EXTRA
|
||||
uint256 expectedAliceDecrease = fee + extra; // fee plus donation
|
||||
|
||||
assertEq(
|
||||
aliceToken0Before - token0.balanceOf(alice),
|
||||
expectedAliceDecrease,
|
||||
"Alice should pay fee + extra"
|
||||
);
|
||||
|
||||
assertEq(
|
||||
token0.balanceOf(address(pool)),
|
||||
poolToken0Before + fee + extra,
|
||||
"Pool should receive fee + extra"
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Test flashRepaymentAmounts matches flash implementation
|
||||
function testFlashRepaymentAmounts() public view {
|
||||
// Create different loan amount scenarios
|
||||
uint256[][] memory testCases = new uint256[][](3);
|
||||
|
||||
// Case 1: Single token
|
||||
testCases[0] = new uint256[](3);
|
||||
testCases[0][0] = 1000;
|
||||
testCases[0][1] = 0;
|
||||
testCases[0][2] = 0;
|
||||
|
||||
// Case 2: Multiple tokens
|
||||
testCases[1] = new uint256[](3);
|
||||
testCases[1][0] = 1000;
|
||||
testCases[1][1] = 2000;
|
||||
testCases[1][2] = 3000;
|
||||
|
||||
// Case 3: Mix of zero and non-zero
|
||||
testCases[2] = new uint256[](3);
|
||||
testCases[2][0] = 0;
|
||||
testCases[2][1] = 2000;
|
||||
testCases[2][2] = 0;
|
||||
|
||||
for (uint256 i = 0; i < testCases.length; i++) {
|
||||
uint256[] memory loanAmounts = testCases[i];
|
||||
uint256[] memory repaymentAmounts = viewer.flashRepaymentAmounts(pool, loanAmounts);
|
||||
|
||||
// Verify each repayment amount is correctly calculated
|
||||
for (uint256 j = 0; j < loanAmounts.length; j++) {
|
||||
if (loanAmounts[j] == 0) {
|
||||
// Zero loans should have zero repayment
|
||||
assertEq(repaymentAmounts[j], 0, "Zero loan should have zero repayment");
|
||||
} else {
|
||||
// Calculate expected repayment with fee
|
||||
uint256 fee = (loanAmounts[j] * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceiling
|
||||
uint256 expectedRepayment = loanAmounts[j] + fee;
|
||||
|
||||
assertEq(
|
||||
repaymentAmounts[j],
|
||||
expectedRepayment,
|
||||
"Repayment calculation mismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
assertEq(
|
||||
fee,
|
||||
expectedFee,
|
||||
"Flash fee calculation mismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Test flash with invalid recipient
|
||||
function testFlashWithZeroRecipientReverts() public {
|
||||
FlashBorrower borrower = setupFlashBorrower();
|
||||
|
||||
// Configure borrower with zero recipient
|
||||
borrower.setAction(FlashBorrower.Action.NORMAL, address(0));
|
||||
|
||||
// Create loan request
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
amounts[0] = 1000;
|
||||
|
||||
// Execute flash loan - should revert due to zero recipient
|
||||
vm.expectRevert(bytes("flash: zero recipient"));
|
||||
borrower.flash(amounts);
|
||||
}
|
||||
|
||||
/// @notice Test flash with incorrect amounts length
|
||||
function testFlashWithIncorrectLengthReverts() public {
|
||||
// Call flash directly with incorrect length
|
||||
uint256[] memory wrongLengthAmounts = new uint256[](2); // Pool has 3 tokens
|
||||
wrongLengthAmounts[0] = 1000;
|
||||
wrongLengthAmounts[1] = 2000;
|
||||
|
||||
vm.expectRevert(bytes("flash: amounts length mismatch"));
|
||||
pool.flash(alice, wrongLengthAmounts, "");
|
||||
}
|
||||
|
||||
/// @notice Test that passing nonzero lpTokens to initialMint doesn't affect swap results
|
||||
/// compared to pools initialized with default lpTokens (0)
|
||||
|
||||
Reference in New Issue
Block a user