function buy(uint256 amount) external whenActive {
require(amount <= state.totalRemaining, "Not enough ZENO remaining");
@>1 uint256 price = getPrice();
uint256 cost = price * amount;
@>2 require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
bidAmounts[msg.sender] += amount;
state.totalRemaining -= amount;
state.lastBidTime = block.timestamp;
state.lastBidder = msg.sender;
zeno.mint(msg.sender, amount);
emit ZENOPurchased(msg.sender, amount, price);
}
function redeem(uint amount) external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
if (amount == 0) {
revert ZeroAmount();
}
uint256 totalAmount = balanceOf(msg.sender);
if (amount > totalAmount) {
revert InsufficientBalance();
}
totalZENORedeemed += amount;
_burn(msg.sender, amount);
@> USDC.safeTransfer(msg.sender, amount);
}
This results in a significant loss for the user effectively, the funds they spent on purchasing ZENO are not fully returned when they redeem.
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "contracts/zeno/AuctionFactory.sol";
import "contracts/zeno/ZENOFactory.sol";
import "contracts/zeno/Auction.sol";
import "contracts/zeno/ZENO.sol";
contract MockUSDC is ERC20 {
constructor() ERC20("USDC", "USDC") {}
function decimals() public pure override returns (uint8) {
return 6;
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract IntegrationTest is Test {
MockUSDC public usdc;
address owner;
address user;
address businessAccount;
AuctionFactory public auctionFactory;
ZENOFactory public zenoFactory;
Auction public auction;
ZENO public zeno;
function setUp() public {
owner = address(this);
user = makeAddr("addr1");
businessAccount = makeAddr("business");
usdc = new MockUSDC();
auctionFactory = new AuctionFactory(owner);
zenoFactory = new ZENOFactory(owner);
uint256 auctionStartTime = block.timestamp ;
uint256 auctionEndTime = auctionStartTime + 1 days;
uint256 startingPrice = 100 * 1e6;
uint256 reservePrice = 10 * 1e6;
uint256 totalZENOAllocated = 100 ether;
uint256 maturityDate = block.timestamp + 365 days;
zenoFactory.createZENOContract(address(usdc), maturityDate);
zeno = zenoFactory.getZENO(0);
auctionFactory.createAuction(
address(zeno),
address(usdc),
businessAccount,
auctionStartTime,
auctionEndTime,
startingPrice,
reservePrice,
totalZENOAllocated
);
auction = auctionFactory.getAuction(0);
zenoFactory.transferZenoOwnership(0, address(auction));
}
function test_POC() public {
uint256 price = auction.getPrice();
uint256 amount = 100 ;
uint256 cost = amount * price;
assertEq(price, 100000000);
assertEq(cost, 10000000000);
usdc.mint(user, cost);
usdc.mint(address(zeno), 1 ether);
vm.startPrank(user);
assertEq(usdc.balanceOf(user), cost);
usdc.approve(address(auction), cost);
vm.warp(block.timestamp + 1);
auction.buy(amount);
assertEq(usdc.balanceOf(user), 104100);
assertEq(zeno.balanceOf(address(user)), amount);
vm.warp(block.timestamp + 365 days);
zeno.redeem(amount);
assertEq(usdc.balanceOf(user), 104200);
vm.stopPrank();
}
}
Before transferring usdc to user, get the price of the redeeming amount of zeno and transfer the cost to the user.