Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use unsigned integers only #8

Merged
merged 3 commits into from
Apr 12, 2024
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Deployments and upgrades can through the `SwapFactory` contract for indexing pur

TenderSwap can also be deployed standlone following the same pattern as the `SwapFactory` contract.

`Factory::deploy` requires an implementation address. Each pool will have its own implementation contract where
`constants` or `immutables` can be specified for each pool. While this adds operational overhead and complexity for
potential upgrades, it significantly improves the gas cost of functions that use these parameters.

### Format

Format the contracts:
Expand Down
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[profile.default]
bytecode_hash = "none"
fuzz = { runs = 10_000 }
fuzz = { runs = 1_000 }
gas_reports = ["*"]
libs = ["lib"]
# optimizer = true (default)
optimizer_runs = 200
optimizer_runs = 100
fs_permissions = [{ access = "read-write", path = "./" }]
solc = "0.8.20"

Expand Down
9 changes: 5 additions & 4 deletions script/Swap_Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Script, console2 } from "forge-std/Script.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { TenderSwap, ConstructorConfig } from "@tenderize/swap/Swap.sol";
import { SwapFactory } from "@tenderize/swap/Factory.sol";
import { SD59x18 } from "@prb/math/SD59x18.sol";
import { UD60x18 } from "@prb/math/UD60x18.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";

address constant FACTORY = address(0);
Expand All @@ -19,14 +19,15 @@ contract Swap_Deploy is Script {
// Start broadcasting with private key from `.env` file
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address underlying = vm.envAddress("UNDERLYING");
SD59x18 BASE_FEE = SD59x18.wrap(vm.envInt("BASE_FEE"));
SD59x18 K = SD59x18.wrap(vm.envInt("K"));
UD60x18 BASE_FEE = UD60x18.wrap(vm.envUint("BASE_FEE"));
UD60x18 K = UD60x18.wrap(vm.envUint("K"));

ConstructorConfig cfg = ConstructorConfig({ UNDERLYING: ERC20(underlying), BASE_FEE: BASE_FEE, K: K });

function run() public {
vm.startBroadcast(deployerPrivateKey);
(address proxy, address implementation) = SwapFactory(FACTORY).deploy(cfg);
address implementation = address(new TenderSwap{ salt: bytes32(uint256(1)) }(cfg));
(address proxy) = SwapFactory(FACTORY).deploy(implementation);
console2.log("Deployment for ", underlying);
console2.log("TenderSwap deployed at: ", proxy);
console2.log("Implementation deployed at: ", implementation);
Expand Down
22 changes: 11 additions & 11 deletions src/Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

pragma solidity ^0.8.20;

import { Owned } from "solmate/auth/Owned.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { TenderSwap, ConstructorConfig } from "@tenderize/swap/Swap.sol";

Expand All @@ -22,8 +21,10 @@ import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/proxy/utils/
// Used for subgraph indexing and atomic deployments

contract SwapFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
error UNDERLYING_MISMATCH();

event SwapDeployed(address underlying, address swap, address implementation);
event SwapUpgraded(address underlying, address swap, address implementation);
event SwapUpgraded(address underlying, address swap, address implementation, uint256 version);

mapping(address pool => uint256 v) public version;

Expand All @@ -36,30 +37,29 @@ contract SwapFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
_disableInitializers();
}

function deploy(ConstructorConfig memory cfg) external onlyOwner returns (address proxy, address implementation) {
function deploy(address implementation) external onlyOwner returns (address proxy) {
uint256 v = 1;
// Deploy the implementation
implementation = address(new TenderSwap{ salt: bytes32(v) }(cfg));
// deploy the contract
proxy = address(
new ERC1967Proxy{ salt: bytes32("tenderswap") }(implementation, abi.encodeWithSelector(TenderSwap.initialize.selector))
);

TenderSwap(proxy).transferOwnership(owner());
version[proxy] = v;
emit SwapDeployed(address(cfg.UNDERLYING), proxy, implementation);
emit SwapDeployed(address(TenderSwap(proxy).UNDERLYING()), proxy, implementation);
}

function upgrade(ConstructorConfig memory cfg, address swapProxy) external onlyOwner returns (address implementation) {
if (TenderSwap(swapProxy).UNDERLYING() != cfg.UNDERLYING) {
revert("SwapFactory: UNDERLYING_MISMATCH");
function upgrade(address newImplementation, address swapProxy) external onlyOwner returns (address implementation) {
address underlying = address(TenderSwap(swapProxy).UNDERLYING());
if (underlying != address(TenderSwap(newImplementation).UNDERLYING())) {
revert UNDERLYING_MISMATCH();
}

uint256 v = ++version[swapProxy];

implementation = address(new TenderSwap{ salt: bytes32(v) }(cfg));

TenderSwap(swapProxy).upgradeTo(implementation);

emit SwapUpgraded(underlying, swapProxy, implementation, v);
}

///@dev required by the OZ UUPS module
Expand Down
93 changes: 51 additions & 42 deletions src/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
//
// Copyright (c) Tenderize Labs Ltd

import { SD59x18, ZERO as ZERO_SD59, UNIT, unwrap, sd } from "@prb/math/SD59x18.sol";
import { UD60x18, ZERO as ZERO_UD60, UNIT as UNIT_60x18, ud } from "@prb/math/UD60x18.sol";
import { UD60x18, ZERO as ZERO_UD60x18, UNIT as UNIT_60x18, ud } from "@prb/math/UD60x18.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { ERC721 } from "solmate/tokens/ERC721.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
Expand Down Expand Up @@ -47,15 +46,15 @@ error ErrorCalculateLPShares();

struct ConstructorConfig {
ERC20 UNDERLYING;
SD59x18 BASE_FEE;
SD59x18 K;
UD60x18 BASE_FEE;
UD60x18 K;
}

struct SwapParams {
SD59x18 u;
SD59x18 U;
SD59x18 s;
SD59x18 S;
UD60x18 u;
UD60x18 U;
UD60x18 s;
UD60x18 S;
}

abstract contract SwapStorage {
Expand All @@ -68,7 +67,7 @@ abstract contract SwapStorage {
// total amount of liabilities owed to LPs
uint256 liabilities;
// sum of token supplies that have outstanding unlocks
SD59x18 S;
UD60x18 S;
// Recovery amount, if `recovery` > 0 enable recovery mode
uint256 recovery;
// treasury share of rewards pending withdrawal
Expand All @@ -78,7 +77,7 @@ abstract contract SwapStorage {
// amount unlocking per asset
mapping(address asset => uint256 unlocking) unlockingForAsset;
// last supply of a tenderizer when seen, tracked because they are rebasing tokens
mapping(address asset => SD59x18 lastSupply) lastSupplyForAsset;
mapping(address asset => UD60x18 lastSupply) lastSupplyForAsset;
// relayer fees
mapping(address relayer => uint256 reward) relayerRewards;
}
Expand Down Expand Up @@ -106,8 +105,8 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
event RelayerRewardsClaimed(address indexed relayer, uint256 rewards);

ERC20 public immutable UNDERLYING;
SD59x18 public immutable BASE_FEE;
SD59x18 public immutable K;
UD60x18 public immutable BASE_FEE;
UD60x18 public immutable K;

// Minimum cut of the fee for LPs when an unlock is bought
UD60x18 public constant MIN_LP_CUT = UD60x18.wrap(0.05e18);
Expand All @@ -131,7 +130,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
_disableInitializers();
}

function lpToken() public view returns (ERC20) {
function lpToken() external view returns (ERC20) {
Data storage $ = _loadStorageSlot();
return ERC20($.lpToken);
}
Expand All @@ -140,7 +139,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Amount of liabilities outstanding to liquidity providers.
* Liabilities represent all the deposits from liquidity providers and their earned fees.
*/
function liabilities() public view returns (uint256) {
function liabilities() external view returns (uint256) {
Data storage $ = _loadStorageSlot();
return $.liabilities;
}
Expand All @@ -159,7 +158,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
*/
function utilisation() public view returns (UD60x18 r) {
Data storage $ = _loadStorageSlot();
if ($.liabilities == 0) return ZERO_UD60;
if ($.liabilities == 0) return ZERO_UD60x18;
r = _utilisation($.unlocking, $.liabilities);
}

Expand Down Expand Up @@ -244,7 +243,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Claim outstanding rewards for a relayer.
* @return relayerReward Amount of tokens claimed
*/
function claimRelayerRewards() public returns (uint256 relayerReward) {
function claimRelayerRewards() external returns (uint256 relayerReward) {
Data storage $ = _loadStorageSlot();

relayerReward = $.relayerRewards[msg.sender];
Expand All @@ -256,7 +255,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
emit RelayerRewardsClaimed(msg.sender, relayerReward);
}

function claimTreasuryRewards() public onlyOwner returns (uint256 treasuryReward) {
function claimTreasuryRewards() external onlyOwner returns (uint256 treasuryReward) {
Data storage $ = _loadStorageSlot();

treasuryReward = $.treasuryRewards;
Expand Down Expand Up @@ -284,12 +283,12 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @return out Amount of output tokens
* @return fee Amount of fees paid
*/
function quote(address asset, uint256 amount) public view returns (uint256 out, uint256 fee) {
function quote(address asset, uint256 amount) external view returns (uint256 out, uint256 fee) {
Data storage $ = _loadStorageSlot();

SD59x18 U = sd(int256($.unlocking));
SD59x18 u = sd(int256($.unlockingForAsset[asset]));
(SD59x18 s, SD59x18 S) = _checkSupply(asset);
UD60x18 U = ud($.unlocking);
UD60x18 u = ud($.unlockingForAsset[asset]);
(UD60x18 s, UD60x18 S) = _checkSupply(asset);

SwapParams memory p = SwapParams({ U: U, u: u, S: S, s: s });
return _quote(amount, p);
Expand All @@ -310,10 +309,10 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS

Data storage $ = _loadStorageSlot();

SD59x18 U = sd(int256($.unlocking));
SD59x18 u = sd(int256($.unlockingForAsset[asset]));
SD59x18 x = sd(int256(amount));
(SD59x18 s, SD59x18 S) = _checkSupply(asset);
UD60x18 U = ud($.unlocking);
UD60x18 u = ud($.unlockingForAsset[asset]);
UD60x18 x = ud(amount);
(UD60x18 s, UD60x18 S) = _checkSupply(asset);

SwapParams memory p = SwapParams({ U: U, u: u, S: S, s: s });

Expand Down Expand Up @@ -402,7 +401,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
// - Update S if unlockingForAsset is now zero
if (ufa == 0) {
$.S = $.S.sub($.lastSupplyForAsset[tenderizer]);
$.lastSupplyForAsset[tenderizer] = ZERO_SD59;
$.lastSupplyForAsset[tenderizer] = ZERO_UD60x18;
}
// - Update unlockingForAsset
$.unlockingForAsset[tenderizer] = ufa;
Expand Down Expand Up @@ -481,7 +480,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
// - Update S if unlockingForAsset is now zero
if (ufa == 0) {
$.S = $.S.sub($.lastSupplyForAsset[tenderizer]);
$.lastSupplyForAsset[tenderizer] = ZERO_SD59;
$.lastSupplyForAsset[tenderizer] = ZERO_UD60x18;
}
// - Update unlockingForAsset
$.unlockingForAsset[tenderizer] = ufa;
Expand All @@ -506,31 +505,41 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
function _quote(uint256 amount, SwapParams memory p) internal view returns (uint256 out, uint256 fee) {
Data storage $ = _loadStorageSlot();

SD59x18 x = sd(int256(amount));
SD59x18 L = sd(int256($.liabilities));
SD59x18 nom;
SD59x18 denom;
UD60x18 x = ud((amount));
UD60x18 L = ud(($.liabilities));
UD60x18 nom;
UD60x18 denom;

// (((u + x)*k - U + u)*((U + x)/L)**k + (-k*u + U - u)*(U/L)**k)*(S + U)/(k*(1 + k)*(s + u))

// in this formula (-k*u + U -u) can be rewritten as U-(k+1)*u
// if U < (k+1)*u then we must do (k+1)*u - U and subtract that from the first part of the sum in the nominator
// else we use the initial formula

{
SD59x18 sumA = p.u.add(x);
UD60x18 sumA = p.u.add(x);
sumA = sumA.mul(K).sub(p.U).add(p.u);
sumA = sumA.mul(p.U.add(x).div(L).pow(K));

SD59x18 sumB = p.U.sub(p.u).sub(K.mul(p.u)).mul(p.U.div(L).pow(K));

nom = sumA.add(sumB).mul(p.S.add(p.U));
UD60x18 negator = K.add(UNIT_60x18).mul(p.u);
if (p.U < negator) {
UD60x18 sumB = negator.sub(p.U).mul(p.U.div(L).pow(K));
nom = sumA.sub(sumB).mul(p.S.add(p.U));
} else {
UD60x18 sumB = p.U.sub(negator).mul(p.U.div(L).pow(K));
nom = sumA.add(sumB).mul(p.S.add(p.U));
}

denom = K.mul(UNIT.add(K)).mul(p.s.add(p.u));
denom = K.mul(UNIT_60x18.add(K)).mul(p.s.add(p.u));
}
SD59x18 baseFee = BASE_FEE.mul(x);
fee = uint256(baseFee.add(nom.div(denom)).unwrap());
UD60x18 baseFee = BASE_FEE.mul(x);
fee = baseFee.add(nom.div(denom)).unwrap();

fee = fee >= amount ? amount : fee;
unchecked {
out = amount - fee;
}
}
// (((u + x)*k - U + u)*((U + x)/L)**k + (-k*u + U - u)*(U/L)**k)*(S + U)/(k*(1 + k)*(s + u))

/**
* @notice checks if an asset is a valid tenderizer for `UNDERLYING`
Expand Down Expand Up @@ -570,13 +579,13 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Since the LSTs to be exchanged are aTokens, and thus have a rebasing supply,
* we need to update the supplies upon a swap to correctly determine the spread of the asset.
*/
function _checkSupply(address tenderizer) internal view returns (SD59x18 s, SD59x18 S) {
function _checkSupply(address tenderizer) internal view returns (UD60x18 s, UD60x18 S) {
Data storage $ = _loadStorageSlot();

S = $.S;

s = sd(int256(Tenderizer(tenderizer).totalSupply()));
SD59x18 oldSupply = $.lastSupplyForAsset[tenderizer];
s = ud(Tenderizer(tenderizer).totalSupply());
UD60x18 oldSupply = $.lastSupplyForAsset[tenderizer];

if (oldSupply.lt(s)) {
S = S.add(s.sub(oldSupply));
Expand Down
5 changes: 2 additions & 3 deletions test/Swap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { Tenderizer, TenderizerImmutableArgs } from "@tenderize/stake/tenderizer
import { TenderSwap, ConstructorConfig, _encodeTokenId, _decodeTokenId } from "@tenderize/swap/Swap.sol";
import { LPToken } from "@tenderize/swap/LPToken.sol";

import { SD59x18, ZERO, UNIT, unwrap, sd } from "@prb/math/SD59x18.sol";
import { UD60x18, ud, UNIT as UNIT_60x18 } from "@prb/math/UD60x18.sol";

import { SwapHarness } from "./Swap.harness.sol";
Expand Down Expand Up @@ -74,7 +73,7 @@ contract TenderSwapTest is Test {
address(tToken1), abi.encodeWithSelector(TenderizerImmutableArgs.asset.selector), abi.encode(address(underlying))
);

ConstructorConfig memory cfg = ConstructorConfig({ UNDERLYING: underlying, BASE_FEE: sd(0.0005e18), K: sd(3e18) });
ConstructorConfig memory cfg = ConstructorConfig({ UNDERLYING: underlying, BASE_FEE: ud(0.0005e18), K: ud(3e18) });
swap = new SwapHarness(cfg);
address proxy = address(new ERC1967Proxy(address(swap), ""));
swap = SwapHarness(proxy);
Expand Down Expand Up @@ -279,7 +278,7 @@ contract TenderSwapTest is Test {
(uint256 out, uint256 fee) = swap.swap(address(tToken0), amount, 0);
console.log("out %s", out);
console.log("fee %s", fee);
// just assert the call doesnt fail for now
assertTrue(fee <= out);
}

function testFuzz_swap_multiple(uint256 liquidity) public {
Expand Down
Loading