diff --git a/.gitmodules b/.gitmodules index b72a377..d178bc2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,6 @@ [submodule "lib/staking"] path = lib/staking url = https://github.com/tenderize/staking +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..65420cb --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 65420cb9c943c32eb7e8c9da60183a413d90067a diff --git a/script/Add_Liquidity.s.sol b/script/Add_Liquidity.s.sol index de02177..b72d247 100644 --- a/script/Add_Liquidity.s.sol +++ b/script/Add_Liquidity.s.sol @@ -17,7 +17,7 @@ contract Add_Liquidity is Script { function run() public { vm.startBroadcast(deployerPrivateKey); - TenderSwap swap = TenderSwap(0x4ec6faD51A1957cAb7E8a62e43f0A0a0c2143d3f); + TenderSwap swap = TenderSwap(0x2C7b29B0d07276bA2DF4abE02E9A38b5693af9c6); ERC20(underlying).approve(address(swap), 500_000 ether); swap.deposit(500_000 ether); console2.log("liabilities", swap.liabilities()); diff --git a/script/Stats.s.sol b/script/Stats.s.sol new file mode 100644 index 0000000..036ef5e --- /dev/null +++ b/script/Stats.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { Script, console2 } from "forge-std/Script.sol"; +import { ERC20 } from "solmate/tokens/ERC20.sol"; +import { TenderSwap, Config } from "@tenderize/swap/Swap.sol"; +import { SD59x18 } from "@prb/math/SD59x18.sol"; +import { Tenderizer } from "@tenderize/stake/tenderizer/Tenderizer.sol"; +import { StakingXYZ } from "lib/staking/test/helpers/StakingXYZ.sol"; + +contract Stats is Script { + function run() public { + address tenderswap = vm.envAddress("TENDERSWAP"); + TenderSwap swap = TenderSwap(tenderswap); + console2.log( + "staking xyz %s", + StakingXYZ(0xd6d72408586887E37Cf299dbb50181892D3b184e).staked(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00) + ); + console2.log("tenderizer asset %s", Tenderizer(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00).asset()); + console2.log("tenderizer validator %s", Tenderizer(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00).validator()); + + console2.log( + "tenderizer bal %s", + Tenderizer(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00).balanceOf(0xF569CE1f749f073D6B85166141544288b3e24c2B) + ); + + console2.log("tenderizer supply %s", ERC20(address(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00)).totalSupply()); + ERC20(address(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00)).approve(tenderswap, 1 ether); + swap.swap(address(0xE3350e66D3850B4f4C97b6737E9e8Ff78CFC1b00), 1 ether, 0); + uint256 liabilities = swap.liabilities(); + uint256 liquidity = swap.liquidity(); + SD59x18 utilisation = swap.utilisation(); + + console2.log("liabilities %s", liabilities); + console2.log("liquidity %s", liquidity); + console2.log("utilisation %s", utilisation.unwrap()); + } +} diff --git a/src/LPToken.sol b/src/LPToken.sol index 8675b2a..c494ab7 100644 --- a/src/LPToken.sol +++ b/src/LPToken.sol @@ -37,10 +37,10 @@ contract LPToken is ERC20 { } function _encodeName(string memory _name) internal pure returns (string memory) { - return string(abi.encodePacked("TenderSwap", " ", _name)); + return string.concat("TenderSwap", " ", _name); } function _encodeSymbol(string memory _symbol) internal pure returns (string memory) { - return string(abi.encodePacked("tSWAP", " ", _symbol)); + return string.concat("tSWAP", " ", _symbol); } } diff --git a/src/Swap.sol b/src/Swap.sol index 7f404c0..f4b13a3 100644 --- a/src/Swap.sol +++ b/src/Swap.sol @@ -20,6 +20,10 @@ import { Tenderizer, TenderizerImmutableArgs } from "@tenderize/stake/tenderizer import { Unlocks } from "@tenderize/stake/unlocks/Unlocks.sol"; import { SafeCastLib } from "solmate/utils/SafeCastLib.sol"; +import { OwnableUpgradeable } from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Initializable } from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + import { Multicall } from "@tenderize/swap/util/Multicall.sol"; import { SelfPermit } from "@tenderize/swap/util/SelfPermit.sol"; import { ERC721Receiver } from "@tenderize/swap/util/ERC721Receiver.sol"; @@ -29,6 +33,7 @@ import { UnlockQueue } from "@tenderize/swap/UnlockQueue.sol"; pragma solidity >=0.8.19; // TODO: UUPS upgradeable +// TODO: fix '_utilisation' to use UD60x18 SD59x18 constant BASE_FEE = SD59x18.wrap(0.0005e18); UD60x18 constant RELAYER_CUT = UD60x18.wrap(0.1e18); @@ -80,7 +85,7 @@ abstract contract SwapStorage { } } -contract TenderSwap is SwapStorage, Multicall, SelfPermit, ERC721Receiver { +contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapStorage, Multicall, SelfPermit, ERC721Receiver { using SafeTransferLib for ERC20; using SafeCastLib for uint256; using UnlockQueue for UnlockQueue.Data; @@ -104,11 +109,18 @@ contract TenderSwap is SwapStorage, Multicall, SelfPermit, ERC721Receiver { address private immutable registry; address private immutable unlocks; + function intialize() public initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + /// @custom:oz-upgrades-unsafe-allow constructor constructor(Config memory config) { lpToken = new LPToken(config.underlying.name(), config.underlying.symbol()); underlying = config.underlying; registry = config.registry; unlocks = config.unlocks; + _disableInitializers(); } modifier supplyUpdateHook(address asset) { @@ -545,6 +557,10 @@ contract TenderSwap is SwapStorage, Multicall, SelfPermit, ERC721Receiver { return amount * supply / $.liabilities; } + + ///@dev required by the OZ UUPS module + // solhint-disable-next-line no-empty-blocks + function _authorizeUpgrade(address) internal override onlyOwner { } } function _encodeTokenId(address tenderizer, uint96 id) pure returns (uint256) { diff --git a/test/z3/quote.py b/test/z3/quote.py new file mode 100644 index 0000000..73ecede --- /dev/null +++ b/test/z3/quote.py @@ -0,0 +1,15 @@ +UNIT = 1e18 +BASE_FEE = 0.005 + + +def quote(x, u, U, s, S, L, K): + sumA = ((u + x) * K - U + u) * ((U + x) / L)**K + + sumB = (U - u - K * u) * (U / L)**K + + nom = (sumA + sumB) * (S + U) + denom = K * (UNIT + K) * (s + u) + + fee = BASE_FEE * x + nom / denom + out = x - fee + return (out, fee) diff --git a/test/z3/quote_solver.py b/test/z3/quote_solver.py new file mode 100644 index 0000000..62567d6 --- /dev/null +++ b/test/z3/quote_solver.py @@ -0,0 +1,48 @@ +from z3 import Solver, Int, sat +import quote + +# Define Z3 variables corresponding to Solidity function inputs and parameters +amount = Int('amount') +L = Int('L') # Liability +U = Int('U') # Example parameter from SwapParams +x = Int('x') # Corresponds to 'amount' in Solidity +K = Int('K') # Some constant from your function +BASE_FEE = Int('BASE_FEE') # Base fee constant +UNIT = Int('1') + +# SwapParams in Z3 (simplified) +p_u = Int('p_u') +p_U = Int('p_U') +p_s = Int('p_s') +p_S = Int('p_S') + +s = Solver() + +# Generate random values (as an example) +# random_amount = random.uniform(0, 1000) # Random value between 0 and 1000 +# ... generate other random values as needed + +# Add random values as constraints +# s.add(amount == random_amount) + +# Define bounds (as an example) +# lower_bound = 10 +# upper_bound = 500 + +# Add constraints for bounds +# s.add(amount >= lower_bound, amount <= upper_bound) + +# Simplified representation of the fee calculation logic +# Note: This is highly simplified and should be replaced with the actual logic +(out, fee) = quote.quote(amount, p_u, p_U, p_s, p_S, L, K) + +# Define invariants + +s.add(out <= amount, fee <= amount, out + fee == + amount, out <= L - U, amount <= p_s) + +# Check if the invariants are satisfiable +if s.check() == sat: + print("Invariants are satisfiable. Function behaves as expected under these conditions.") +else: + print("Invariants are not satisfiable. Function may have an issue or the model may need refinement.")