Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
be6554c
skeleton
pjol Sep 26, 2025
685c918
zapIn and zapOut implemented, no tests
pjol Oct 6, 2025
c08fd39
working on zapper tests, zapper is not initializing
SanchezOLeary Oct 8, 2025
5ecea1a
initializer modifier added to zapper
pjol Oct 8, 2025
4383047
migrator check-in
pjol Jan 16, 2026
382bc2a
zap and bridge functionality added
pjol Jan 16, 2026
cf632c8
stashing before branch switch
SanchezOLeary Jan 17, 2026
ac0996a
making new dev branch
SanchezOLeary Jan 17, 2026
cb393ac
stashing before switch
SanchezOLeary Jan 17, 2026
6385c94
checking in before trying to fix remappings
SanchezOLeary Jan 21, 2026
e8c806f
zapper finished and needs security review before deployment
SanchezOLeary Jan 22, 2026
50473ba
WIP - review and tweaks.
paulgoleary Jan 23, 2026
f7b5bb8
WIP - more
paulgoleary Jan 23, 2026
a858612
changed unwrap function parameters
SanchezOLeary Jan 23, 2026
beb2318
WIP - fixes reentrancy. correct byusd in honey calc. etc.
paulgoleary Jan 25, 2026
a6d167a
fixed liquidity pool bug and wrote test for it
SanchezOLeary Jan 27, 2026
455e002
Added test for unwrapSwapAndBridge that does redeem and liquidity poo…
SanchezOLeary Jan 29, 2026
7669dbf
Improved test coverage, added comments to tests, removed useless logs
SanchezOLeary Jan 29, 2026
e669b26
WIP - initial upgrader, etc.
paulgoleary Jan 30, 2026
f853b36
commiting before starting USDC zapper
SanchezOLeary Feb 5, 2026
b688ac6
merging
SanchezOLeary Feb 5, 2026
c33de11
vibe coded a USDC->Base zapper
SanchezOLeary Feb 5, 2026
12e81f7
Handle Honey basket mode by skipping redeem in unwrap flow
SanchezOLeary Feb 12, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
cache/
out/

broadcast/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
Expand Down
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@
[submodule "lib/openzeppelin-foundry-upgrades"]
path = lib/openzeppelin-foundry-upgrades
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
[submodule "lib/contracts"]
path = lib/contracts
url = https://github.com/berachain/contracts
[submodule "lib/bera-contracts"]
path = lib/bera-contracts
url = https://github.com/berachain/contracts
[submodule "lib/layerzero"]
path = lib/layerzero
url = https://github.com/LayerZero-Labs/devtools
[submodule "lib/lz-address-book"]
path = lib/lz-address-book
url = https://github.com/LayerZero-Labs/lz-address-book
16 changes: 14 additions & 2 deletions NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,25 @@ cast balance --erc20 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 0xf39Fd6e51aad88
# sfluv balance = anvil address 0
cast balance --erc20 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:8545

forge verify-contract 0x881cAd4f885c6701D8481c0eD347f6d35444eA7e src/SFLUVv2.sol:SFLUVv2 --verifier-url 'https://api.berascan.com/api' --etherscan-api-key WMP6HJHJ9NAK8A63SNAG1G4KHAUKU5X4SQ --num-of-optimizations 200
forge verify-contract 0x881cAd4f885c6701D8481c0eD347f6d35444eA7e src/SFLUVv2.sol:SFLUVv2 --verifier-url 'https://api.berascan.com/api' --etherscan-api-key $$ETHERSCAN_API_KEY --num-of-optimizations 200

forge verify-contract --watch --chain-id 80094 0x881cAd4f885c6701D8481c0eD347f6d35444eA7e src/SFLUVv2.sol:SFLUVv2 --verifier-url 'https://api.berascan.com/api'

forge verify-contract 0x881cAd4f885c6701D8481c0eD347f6d35444eA7e \
src/SFLUVv2.sol:SFLUVv2 \
--rpc-url https://rpc.berachain.com \
--etherscan-api-key WMP6HJHJ9NAK8A63SNAG1G4KHAUKU5X4SQ \
--etherscan-api-key $$ETHERSCAN_API_KEY \
--verifier-url 'https://api.berascan.com/api' \
--watch

# deploy zapper
forge script script/DeploySFLUVZapperv1.s.sol:DeploySFLUVZapperv1 --rpc-url https://rpc.berachain.com --private-key $PRIVATE_KEY --broadcast
0xd0EBD0495750899D18b915BDeba789E2defdC394

forge script script/UpgradeSFLUVZapper.sol:UpgradeSFLUVZapper \
--rpc-url https://rpc.berachain.com \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
-vvvv

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions broadcast/DeploySFLUVUSDCeZapper.s.sol/80094/run-1770331165315.json

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions broadcast/DeploySFLUVUSDCeZapper.s.sol/80094/run-latest.json

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"lib/bera-contracts": {
"rev": "b7562f2bcd4bef30c9833469ddb98b8fbc419cc9"
},
"lib/contracts": {
"rev": "b7562f2bcd4bef30c9833469ddb98b8fbc419cc9"
},
"lib/forge-std": {
"rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505"
},
"lib/layerzero": {
"rev": "6c95cd3c1d96ae9b3594a352f6f209f3a7733bcb"
},
"lib/openzeppelin-contracts": {
"rev": "e4f70216d759d8e6a64144a9e1f7bbeed78e7079"
},
"lib/openzeppelin-contracts-upgradeable": {
"rev": "60b305a8f3ff0c7688f02ac470417b6bbf1c4d27"
},
"lib/openzeppelin-foundry-upgrades": {
"rev": "cbce1e00305e943aa1661d43f41e5ac72c662b07"
}
}
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ build_info = true
extra_output = ["storageLayout"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

[etherscan]
berachain = { key = "${ETHERSCAN_API_KEY}", url = "https://rpc.berachain.com" }
1 change: 1 addition & 0 deletions lib/bera-contracts
Submodule bera-contracts added at a52caf
1 change: 1 addition & 0 deletions lib/contracts
Submodule contracts added at c20aac
1 change: 1 addition & 0 deletions lib/layerzero
Submodule layerzero added at 6c95cd
1 change: 1 addition & 0 deletions lib/lz-address-book
Submodule lz-address-book added at ea3e01
10 changes: 9 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/
@berachain/contracts=lib/bera-contracts/src/
solady/src/=lib/bera-contracts/lib/solady/src
@layerzerolabs/lz-evm-protocol-v2/=lib/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/
@layerzerolabs/lz-evm-messagelib-v2/=lib/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/messagelib/
@layerzerolabs/oft-evm/=lib/lz-address-book/lib/devtools/packages/oft-evm/
@layerzerolabs/oapp-evm/=lib/lz-address-book/lib/devtools/packages/oapp-evm/
@openzeppelin/contracts/=lib/lz-address-book/lib/openzeppelin-contracts/contracts/
solidity-bytes-utils/=lib/lz-address-book/lib/solidity-bytes-utils/
forge-std/=lib/forge-std/src/
57 changes: 57 additions & 0 deletions script/DeploySFLUVUSDCeZapper.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import "forge-std/Script.sol";
import {SFLUVUSDCeZapper, SFLUVUSDCeZapperStorageInit} from "../src/SFLUVUSDCeZapper.sol";
import {SFLUVv2} from "../src/SFLUVv2.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IDeployErrors} from "./IDeployErrors.sol";

contract DeploySFLUVUSDCeZapper is Script, IDeployErrors {

function run() public {
address honeyFactory = vm.envAddress("HONEY_FACTORY_ADDRESS");
address sfluv = vm.envAddress("SFLUV_ADDRESS");
address honey = vm.envAddress("HONEY_ADDRESS");
address usdc = vm.envAddress("USDC_E_ADDRESS");
address usdcAdapter = vm.envAddress("USDC_E_ADAPTER_ADDRESS");
address usdcVault = vm.envAddress("USDC_E_VAULT_ADDRESS");
address honeyToUSDCPool = vm.envAddress("HONEY_USDC_POOL_ADDRESS");
uint32 usdcDstEid = uint32(vm.envUint("USDC_DST_EID"));

SFLUVUSDCeZapperStorageInit memory s = SFLUVUSDCeZapperStorageInit(
honeyFactory,
sfluv,
honey,
usdc,
usdcAdapter,
usdcVault,
honeyToUSDCPool,
usdcDstEid
);

vm.startBroadcast();

SFLUVv2 token = SFLUVv2(sfluv);

bool isAdmin = token.hasRole(token.DEFAULT_ADMIN_ROLE(), msg.sender);
if (!isAdmin) revert NotAdmin();

bool isRedeemerAdmin = token.hasRole(token.REDEEMER_ADMIN_ROLE(), msg.sender);
if (!isRedeemerAdmin) {
token.grantRole(token.REDEEMER_ADMIN_ROLE(), msg.sender);
}

SFLUVUSDCeZapper impl = new SFLUVUSDCeZapper();

ERC1967Proxy proxy = new ERC1967Proxy(address(impl),
abi.encodeCall(impl.initialize, (msg.sender, s))
);

token.grantRole(token.REDEEMER_ROLE(), address(proxy));

vm.stopBroadcast();

console.log("UUPS Proxy Address:", address(proxy));
}
}
53 changes: 53 additions & 0 deletions script/DeploySFLUVZapperv1.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pragma solidity ^0.8.26;

import "forge-std/Script.sol";
import {SFLUVZapperv1, SFLUVZapperStorageInit} from "../src/SFLUVZapperv1.sol";
import {SFLUVv2} from "../src/SFLUVv2.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDeployErrors} from "./IDeployErrors.sol";

contract DeploySFLUVZapperv1 is Script, IDeployErrors {

function run() public {
address honeyFactory = vm.envAddress("HONEY_FACTORY_ADDRESS");
address sfluv = vm.envAddress("SFLUV_ADDRESS");
address byusd = vm.envAddress("BYUSD_ADDRESS");
address honey = vm.envAddress("HONEY_ADDRESS");
address honeyToBYUSDPool = vm.envAddress("HONEY_BYUSD_POOL_ADDRESS");
address byusdVault = vm.envAddress("BYUSD_VAULT_ADDRESS");

SFLUVZapperStorageInit memory s = SFLUVZapperStorageInit(
honeyFactory,
sfluv,
byusd,
honey,
honeyToBYUSDPool,
byusdVault
);

vm.startBroadcast();

SFLUVv2 token = SFLUVv2(sfluv);

bool isAdmin = token.hasRole(token.DEFAULT_ADMIN_ROLE(), msg.sender);
if (!isAdmin) revert NotAdmin();

bool isMinterAdmin = token.hasRole(token.MINTER_ADMIN_ROLE(), msg.sender);
require(isMinterAdmin);
bool isRedeemerAdmin = token.hasRole(token.REDEEMER_ADMIN_ROLE(), msg.sender);
require(isRedeemerAdmin);

SFLUVZapperv1 impl = new SFLUVZapperv1();

ERC1967Proxy proxy = new ERC1967Proxy(address(impl),
abi.encodeCall(impl.initialize, (msg.sender, s)));

token.grantRole(token.MINTER_ROLE(), address(proxy));
token.grantRole(token.REDEEMER_ROLE(), address(proxy));

vm.stopBroadcast();

console.log("UUPS Proxy Address:", address(proxy));
}
}
7 changes: 4 additions & 3 deletions script/GrantSFLUVv2.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ contract GrantSFLUVv2 is Script {
address constant private SFLUV_V2_ADDR = 0x881cAd4f885c6701D8481c0eD347f6d35444eA7e;

// mainnet gov account
address constant private TARGET_ADDR = 0x90496e23825aD0C8107d04671e6a27f30630Fc35;
address constant private TARGET_ADDR = 0x8b631C26537a784082A55528eaD52271ce88572e;

function run() public {
vm.startBroadcast();
SFLUVv2 impl = SFLUVv2(SFLUV_V2_ADDR);
impl.grantRole(impl.MINTER_ADMIN_ROLE(), TARGET_ADDR);
// impl.grantRole(impl.MINTER_ADMIN_ROLE(), TARGET_ADDR);
impl.grantRole(impl.REDEEMER_ROLE(), TARGET_ADDR);
vm.stopBroadcast();
}
}
}
9 changes: 9 additions & 0 deletions script/IDeployErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

interface IDeployErrors {
// Signature: 0xd92e233d
error ZeroAddress();

error NotAdmin();
}
15 changes: 15 additions & 0 deletions script/SFLUVStorageSlots.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {Script, console} from "forge-std/Script.sol";

contract SFLUVStorageSlotScript is Script {
function setUp() public {}

function run() public pure {
bytes32 SFLUVZapperStorage =
keccak256(abi.encode(uint256(keccak256("SFLUV.storage.SFLUVZapper")) - 1)) & ~bytes32(uint256(0xff));

console.log("SFLUVZapper Storage: ", vm.toString(SFLUVZapperStorage));
}
}
39 changes: 39 additions & 0 deletions script/UpgradeSFLUVZapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pragma solidity ^0.8.26;

import "forge-std/Script.sol";
import {SFLUVZapperv1} from "../src/SFLUVZapperv1.sol";
// import {SFLUVZapperv2} from "../src/SFLUVZapperv2.sol"; // Your new implementation
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {IDeployErrors} from "./IDeployErrors.sol";

interface IUUPSUpgradeable {
function upgradeToAndCall(address newImplementation, bytes memory data) external payable;
}

contract UpgradeSFLUVZapper is Script, IDeployErrors {

function run() public {
address payable proxyAddress = payable(vm.envAddress("SFLUV_ZAPPER_PROXY_ADDRESS"));

vm.startBroadcast();

// Get reference to the proxy (using the old interface)
SFLUVZapperv1 proxy = SFLUVZapperv1(proxyAddress);

// Verify caller has upgrade authority
if (!proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), msg.sender)) revert NotAdmin();

// Deploy new implementation
SFLUVZapperv1 newImpl = new SFLUVZapperv1();

console.log("New Implementation:", address(newImpl));

// For simple upgrade without reinitialization:
IUUPSUpgradeable(proxyAddress).upgradeToAndCall(address(newImpl), "");

console.log("Upgrade successful!");
console.log("Proxy Address:", proxyAddress);

vm.stopBroadcast();
}
}
30 changes: 30 additions & 0 deletions src/ILiquidityPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

interface ILiquidityPool {
/// Swap token0 <-> token1
/// @param recipient Address receiving output tokens
/// @param zeroForOne True = token0 → token1, false = token1 → token0
/// @param amountSpecified Positive = exact input, negative = exact output
/// @param sqrtPriceLimitX96 Q64.96 price limit, 0 = no limit
/// @param data Bytes passed to callback
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0Delta, int256 amount1Delta);

function token0() external view returns (address);
function token1() external view returns (address);
function slot0() external view returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint32 feeProtocol,
bool unlocked
);
}
11 changes: 11 additions & 0 deletions src/ISFLUVZapperErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

interface ISFLUVZapperErrors {
// Signature: 0xd92e233d
error ZeroAddress();

error TransferFailed(address token, address to, address from, uint256 value);
error MintFailed();
error RedeemFailed();
}
Loading