Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions evm/modules/01_Market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default buildModule('Market', (m) => {
pexfi,
universalRouter,
m.getParameter('weth_address'),
[zeroAddress, pexfi, 10000, 60, zeroAddress],
[zeroAddress, pexfi, 10000, 200, zeroAddress],
])
m.call(Market, 'changeImplementationAddress', [bytes32('FeeCollector'), feeCollector], {
id: 'regFeeCollector',
Expand Down Expand Up @@ -129,9 +129,9 @@ export default buildModule('Market', (m) => {
id: 'collateralWhitelist',
})
m.call(CollateralWhitelist, 'addToWhitelist', [pexfiVault])
// With OOv3 defaults (50% burn), Minimum Bond = Final Fee * 2 = 200,000 tokens.
// With OOv3 defaults (50% burn), Minimum Bond = Final Fee * 2 = 200 tokens.
// This ensures only the deployer can assert/dispute initially.
const initialFinalFee = 100_000n * 10n ** 18n
const initialFinalFee = 100n * 10n ** 18n
m.call(Store, 'setFinalFee', [pexfiVault, { rawValue: initialFinalFee }])

// Placeholder Oracle address — syncUmaParams requires it at deploy time.
Expand Down
23 changes: 23 additions & 0 deletions evm/modules/03_BuyPexfi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'
import { parseEther } from 'viem'

/**
* This module performs a buyback of PEXFI tokens from the Uniswap v4 pool.
* It uses a standalone action contract to perform the swap in a single transaction.
*/
export default buildModule('BuyPexfi', (m) => {
const amount = parseEther('0.0003')
const pexfi = m.getParameter('pexfi_address', '0xCCA91e36E5c15163C5258832C57774072Db257C5')
const universalRouter = m.getParameter('uniswapUniversalRouter')

// Deploy the action contract
const action = m.contract('PexfiBuybackAction')

// Execute the buyback
m.call(action, 'run', [universalRouter, pexfi], {
id: 'buybackPexfi',
value: amount,
})

return { action }
})
80 changes: 80 additions & 0 deletions evm/protocol/PexfiBuybackAction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.34;

import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @title PexfiBuybackAction
* @notice Standalone contract to buy PEXFI from Uniswap v4 pool in a single transaction.
*/
contract PexfiBuybackAction {
function run(address router, address pexfi) external payable {
require(msg.value > 0, "No ETH sent");

PoolKey memory poolKey = PoolKey({
currency0: Currency.wrap(address(0)),
currency1: Currency.wrap(pexfi),
fee: 10000,
tickSpacing: 200,
hooks: IHooks(address(0))
});

bytes memory v4Actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE),
uint8(Actions.SETTLE),
uint8(Actions.TAKE_ALL)
);

bytes[] memory v4Inputs = new bytes[](3);

// 1. Swap ETH for PEXFI
v4Inputs[0] = abi.encode(
poolKey,
true, // zeroForOne (ETH is currency0)
uint128(msg.value),
uint128(0), // amountOutMinimum
"" // hookData
);

// 2. Settle ETH (pay from the ETH sent to this contract)
v4Inputs[1] = abi.encode(
Currency.wrap(address(0)),
msg.value,
false // payerIsUser = false (take from this contract)
);

// 3. Take PEXFI (send to this contract)
v4Inputs[2] = abi.encode(
Currency.wrap(pexfi),
0 // minAmount
);

bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.V4_SWAP)));
bytes[] memory inputs = new bytes[](1);
inputs[0] = abi.encode(v4Actions, v4Inputs);

// Execute the swap on Universal Router
IUniversalRouter(router).execute{value: msg.value}(commands, inputs, block.timestamp + 600);

// Send bought PEXFI tokens back to the caller
uint256 pexfiBal = IERC20(pexfi).balanceOf(address(this));
if (pexfiBal > 0) {
bool success = IERC20(pexfi).transfer(msg.sender, pexfiBal);
require(success, "Transfer failed");
}

// Send back any remaining ETH
if (address(this).balance > 0) {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "ETH refund failed");
}
}

receive() external payable {}
}
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions tests/hardhat/ignition/03_BuyPexfi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, test } from 'node:test'
import * as assert from 'node:assert'
import BuyPexfiModule from '@evm/modules/03_BuyPexfi.ts'
import deploy from './01_Market.test.ts'

describe('BuyPexfi Module', () => {
test('should execute buyback successfully', async () => {
const { ignition, universalRouter, pexfi } = await deploy()

const parameters = {
BuyPexfi: {
pexfi_address: pexfi.address,
uniswapUniversalRouter: universalRouter.address,
},
}

// Deploy and run the BuyPexfi module
const { action } = await ignition.deploy(BuyPexfiModule, { parameters })

assert.ok(action.address, 'PexfiBuybackAction should be deployed')
})
})
Loading