From ce99bf0495d7d233586c2e0c6792352da84d70aa Mon Sep 17 00:00:00 2001 From: cwsnt Date: Fri, 24 May 2024 13:26:43 +0700 Subject: [PATCH 1/5] feat: support fee sharing collector --- contracts/callpaths/ColdPath.sol | 15 ++++++++---- contracts/libraries/ProtocolCmd.sol | 9 +++++-- contracts/test/MockTimelock.sol | 5 ++++ test/TestPool.govern.ts | 37 +++++++++++++---------------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/contracts/callpaths/ColdPath.sol b/contracts/callpaths/ColdPath.sol index ede808e..d14a289 100644 --- a/contracts/callpaths/ColdPath.sol +++ b/contracts/callpaths/ColdPath.sol @@ -34,6 +34,12 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { using Chaining for Chaining.PairFlow; using ProtocolCmd for bytes; + /* @dev access control for treasury role */ + modifier onlyTreasury() { + require(msg.sender == treasury_, "Only Treasury"); + _; + } + /* @notice Consolidated method for protocol control related commands. */ function protocolCmd (bytes calldata cmd) virtual public { uint8 code = uint8(cmd[31]); @@ -71,9 +77,7 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { require(sudoMode_, "Sudo"); uint8 cmdCode = uint8(cmd[31]); - if (cmdCode == ProtocolCmd.COLLECT_TREASURY_CODE) { - collectProtocol(cmd); - } else if (cmdCode == ProtocolCmd.SET_TREASURY_CODE) { + if (cmdCode == ProtocolCmd.SET_TREASURY_CODE) { setTreasury(cmd); } else if (cmdCode == ProtocolCmd.AUTHORITY_TRANSFER_CODE) { transferAuthority(cmd); @@ -110,6 +114,8 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { resetNonceCond(cmd); } else if (cmdCode == UserCmd.GATE_ORACLE_COND) { checkGateOracle(cmd); + } else if (cmdCode == UserCmd.COLLECT_TREASURY_CODE) { + collectProtocol(cmd); } else { revert("Invalid command"); } @@ -242,7 +248,7 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { /* @notice Pays out the the protocol fees. * @param token The token for which the accumulated fees are being paid out. * (Or if 0x0 pays out native Ethereum.) */ - function collectProtocol (bytes calldata cmd) private { + function collectProtocol (bytes calldata cmd) private onlyTreasury { (, address token) = abi.decode(cmd, (uint8, address)); require(block.timestamp >= treasuryStartTime_, "Treasury start"); @@ -257,7 +263,6 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { require(treasury != address(0) && treasury.code.length != 0, "Treasury invalid"); treasury_ = treasury; - treasuryStartTime_ = uint64(block.timestamp + 7 days); emit SdexEvents.TreasurySet(treasury_, treasuryStartTime_); } diff --git a/contracts/libraries/ProtocolCmd.sol b/contracts/libraries/ProtocolCmd.sol index 70f51b6..2a2113b 100644 --- a/contracts/libraries/ProtocolCmd.sol +++ b/contracts/libraries/ProtocolCmd.sol @@ -27,8 +27,6 @@ library ProtocolCmd { uint8 constant HOT_OPEN_CODE = 22; // Code to toggle on or off emergency safe mode uint8 constant SAFE_MODE_CODE = 23; - // Code to collect accumulated protocol fees for the treasury. - uint8 constant COLLECT_TREASURY_CODE = 40; // Code to set the protocol treasury uint8 constant SET_TREASURY_CODE = 41; //////////////////////////////////////////////////////////////////////////// @@ -113,4 +111,11 @@ library UserCmd { uint8 constant BURN_KNOCKOUT = 92; uint8 constant CLAIM_KNOCKOUT = 93; uint8 constant RECOVER_KNOCKOUT = 94; + + + //////////////////////////////////////////////////////////////////////////// + // Protocol Fee command codes + //////////////////////////////////////////////////////////////////////////// + // Code to collect accumulated protocol fees for the treasury. + uint8 constant COLLECT_TREASURY_CODE = 40; } diff --git a/contracts/test/MockTimelock.sol b/contracts/test/MockTimelock.sol index c5170e4..64219f5 100644 --- a/contracts/test/MockTimelock.sol +++ b/contracts/test/MockTimelock.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import "../governance/SdexPolicy.sol"; +import "../SdexSwapDex.sol"; import "hardhat/console.sol"; contract MockTimelock { @@ -47,4 +48,8 @@ contract MockTimelock { return SdexPolicy(policy_).setPolicy(conduit, proxyPath, policy); } + function userCmd (address minion, uint16 proxyPath, + bytes calldata cmd) public { + SdexSwapDex(minion).userCmd(proxyPath, cmd); + } } diff --git a/test/TestPool.govern.ts b/test/TestPool.govern.ts index 9eeacdb..b7be25c 100644 --- a/test/TestPool.govern.ts +++ b/test/TestPool.govern.ts @@ -148,44 +148,39 @@ describe('Pool Governance', () => { }) - it("collect treasury", async() => { + it("successfully collect treasury without time delay", async() => { await test.testRevisePool(feeRate, 128, 1) // Turn on protocol fee - await pool.connect(await test.auth).protocolCmd(test.COLD_PROXY, transferCmd(policy.address), true) + await pool.connect(await test.auth).protocolCmd(test.COLD_PROXY, transferCmd(policy2.address), true) await test.testMintAmbient(10000) await test.testSwap(true, false, 100000, MAX_PRICE) - await treasury.treasuryResolution(pool.address, test.COLD_PROXY, treasurySetCmd(policy2.address), true) - await hre.ethers.provider.send("evm_increaseTime", [3600*24*7+1]) // 7 days + await treasury2.treasuryResolution(pool.address, test.COLD_PROXY, treasurySetCmd(treasury2.address), true) // Unauthorized attempts to collect treasury - await expect(pool.protocolCmd(test.COLD_PROXY, collectCmd(), true)).to.be.reverted - await expect(ops.opsResolution(pool.address, test.COLD_PROXY, collectCmd())).to.be.reverted - await expect(ops.treasuryResolution(pool.address, test.COLD_PROXY, collectCmd(), true)).to.be.reverted + await expect(treasury.userCmd(pool.address, test.COLD_PROXY, collectCmd())).to.be.revertedWith("Only Treasury") // Successful treasury payout - let snap = await (await test.query).querySurplus(policy2.address, baseToken.address) - await treasury.treasuryResolution(pool.address, test.COLD_PROXY, collectCmd(), true) - expect(await (await test.query).querySurplus(policy2.address, baseToken.address)).to.gt(snap); + let snap = await (await test.query).querySurplus(treasury2.address, baseToken.address) + await treasury2.userCmd(pool.address, test.COLD_PROXY, collectCmd()) + expect(await (await test.query).querySurplus(treasury2.address, baseToken.address)).to.gt(snap); }) - it("collect treasury time delay", async() => { + it("successfully collect treasury with time delay", async() => { await test.testRevisePool(feeRate, 128, 1) // Turn on protocol fee - await pool.connect(await test.auth).protocolCmd(test.COLD_PROXY, transferCmd(policy.address), true) + await pool.connect(await test.auth).protocolCmd(test.COLD_PROXY, transferCmd(policy2.address), true) await test.testMintAmbient(10000) await test.testSwap(true, false, 100000, MAX_PRICE) - await treasury.treasuryResolution(pool.address, test.COLD_PROXY, treasurySetCmd(policy2.address), true) + await treasury2.treasuryResolution(pool.address, test.COLD_PROXY, treasurySetCmd(treasury2.address), true) - // Will fail because treasury can only be collected 7 days after treasury address is set - await expect(treasury.treasuryResolution(pool.address, test.COLD_PROXY, collectCmd(), true)).to.be.reverted - await hre.ethers.provider.send("evm_increaseTime", [3600*24*6]) // 6 days - await expect(treasury.treasuryResolution(pool.address, test.COLD_PROXY, collectCmd(), true)).to.be.reverted - await hre.ethers.provider.send("evm_increaseTime", [3600*24+1]) // One more day... treasury valid + // Unauthorized attempts to collect treasury + await expect(treasury.userCmd(pool.address, test.COLD_PROXY, collectCmd())).to.be.revertedWith("Only Treasury") + await hre.ethers.provider.send("evm_increaseTime", [3600*24*6]) // 6 days // Successful treasury payout - let snap = await (await test.query).querySurplus(policy2.address, baseToken.address) - await treasury.treasuryResolution(pool.address, test.COLD_PROXY, collectCmd(), true) - expect(await (await test.query).querySurplus(policy2.address, baseToken.address)).to.gt(snap); + let snap = await (await test.query).querySurplus(treasury2.address, baseToken.address) + await treasury2.userCmd(pool.address, test.COLD_PROXY, collectCmd()) + expect(await (await test.query).querySurplus(treasury2.address, baseToken.address)).to.gt(snap); }) From 9f4da78410a68a63fc16805024c3ff5a12c9e6fe Mon Sep 17 00:00:00 2001 From: cwsnt Date: Tue, 4 Jun 2024 09:01:31 +0700 Subject: [PATCH 2/5] init: convertion lib dex --- contracts/SdexEvents.sol | 15 + contracts/callpaths/ColdPath.sol | 57 +++- contracts/callpaths/LongPath.sol | 31 ++ contracts/callpaths/SafeModePath.sol | 2 +- .../interfaces/IFeeProtocolCollector.sol | 8 + contracts/interfaces/ISdexQuery.sol | 8 + contracts/lens/SdexQuery.sol | 43 +++ contracts/libraries/BytesConcat.sol | 30 ++ contracts/libraries/ProtocolCmd.sol | 8 + contracts/libraries/SdexConvertLib.sol | 275 ++++++++++++++++++ contracts/mixins/PoolRegistry.sol | 44 +++ contracts/mixins/ProtocolAccount.sol | 31 +- contracts/mixins/StorageLayout.sol | 17 ++ contracts/test/TestProtocolAcct.sol | 4 +- test/FacadePool.ts | 6 + 15 files changed, 569 insertions(+), 10 deletions(-) create mode 100644 contracts/interfaces/IFeeProtocolCollector.sol create mode 100644 contracts/interfaces/ISdexQuery.sol create mode 100644 contracts/libraries/BytesConcat.sol create mode 100644 contracts/libraries/SdexConvertLib.sol diff --git a/contracts/SdexEvents.sol b/contracts/SdexEvents.sol index 184921b..5a1ed42 100644 --- a/contracts/SdexEvents.sol +++ b/contracts/SdexEvents.sol @@ -98,4 +98,19 @@ library SdexEvents { /** LP Token */ event PoolLpTokenSet (address indexed base, address indexed quote, uint256 poolIdx, address indexed lpToken); event LpTokenDeployerSet (address indexed oldLpTokenDeployerAddress, address indexed newLpTokenDeployerAddress); + event WrappedNativeTokenAddressSet (address indexed oldWrappedNativeTokenAddress, address indexed newWrappedNativeTokenAddress); + event SovTokenAddressSet (address indexed oldSovTokenAddress, address indexed newSovTokenAddress); + event SdexQueryAddressSet (address indexed oldSdexQueryAddress, address indexed newSdexQueryAddress); + + /** Swap */ + /* @notice Emitted when a new defaultPathConversion is added / modified. + * @param sourceTokenAddress source of token address for swap. + * @param destTokenAddress target token address for swap. + * @param defaultPath route path for the swap. + */ + event SetDefaultPathConversion( + address indexed sourceTokenAddress, + address indexed destTokenAddress, + address[] defaultPath + ); } diff --git a/contracts/callpaths/ColdPath.sol b/contracts/callpaths/ColdPath.sol index d14a289..109cbd3 100644 --- a/contracts/callpaths/ColdPath.sol +++ b/contracts/callpaths/ColdPath.sol @@ -64,6 +64,12 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { setPoolLpToken(cmd); } else if (code == ProtocolCmd.SET_LP_TOKEN_DEPLOYER_CODE) { setLpTokenDeployerAddress(cmd); + } else if (code == ProtocolCmd.SET_WRAPPED_NATIVE_TOKEN_CODE) { + setWrappedNativeTokenAddress(cmd); + } else if (code == ProtocolCmd.SET_SOV_TOKEN_CODE) { + setSovTokenAddress(cmd); + } else if (code == ProtocolCmd.SET_SDEX_QUERY_CODE) { + setSovTokenAddress(cmd); } else { sudoCmd(cmd); } @@ -91,7 +97,7 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { } - function userCmd (bytes calldata cmd) virtual public payable { + function userCmd (bytes calldata cmd) virtual public payable returns(bytes memory) { uint8 cmdCode = uint8(cmd[31]); if (cmdCode == UserCmd.INIT_POOL_CODE) { @@ -115,7 +121,8 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { } else if (cmdCode == UserCmd.GATE_ORACLE_COND) { checkGateOracle(cmd); } else if (cmdCode == UserCmd.COLLECT_TREASURY_CODE) { - collectProtocol(cmd); + uint256 convertedAmountToWrappedNativeToken = collectProtocol(cmd); + return abi.encodePacked(convertedAmountToWrappedNativeToken); } else { revert("Invalid command"); } @@ -248,12 +255,12 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { /* @notice Pays out the the protocol fees. * @param token The token for which the accumulated fees are being paid out. * (Or if 0x0 pays out native Ethereum.) */ - function collectProtocol (bytes calldata cmd) private onlyTreasury { + function collectProtocol (bytes calldata cmd) private onlyTreasury returns(uint256) { (, address token) = abi.decode(cmd, (uint8, address)); require(block.timestamp >= treasuryStartTime_, "Treasury start"); emit SdexEvents.ProtocolDividend(token, treasury_); - disburseProtocolFees(treasury_, token); + return disburseProtocolFees(treasury_, token); } /* @notice Sets the treasury address to receive protocol fees. Once set, the treasury cannot @@ -385,5 +392,47 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { emit SdexEvents.LpTokenDeployerSet(lpTokenDeployerAddress, newLpTokenDeployerAddress); setLpTokenDeployerAddress(newLpTokenDeployerAddress); } + + /** + * @dev set wrapped native token address + * @param input bytes that acontains of: + * - protocol action code + * - new wrappedNativeTokenAddress + */ + function setWrappedNativeTokenAddress (bytes calldata input) internal { + (, address newWrappedNativeTokenAddress) = + abi.decode(input, (uint8, address)); + + emit SdexEvents.WrappedNativeTokenAddressSet(wrappedNativeTokenAddress, newWrappedNativeTokenAddress); + setWrappedNativeTokenAddress(newWrappedNativeTokenAddress); + } + + /** + * @dev set sov token address + * @param input bytes that acontains of: + * - protocol action code + * - new sovTokenAddress + */ + function setSovTokenAddress (bytes calldata input) internal { + (, address newSovTokenAddress) = + abi.decode(input, (uint8, address)); + + emit SdexEvents.SovTokenAddressSet(sovTokenAddress, newSovTokenAddress); + setSovTokenAddress(newSovTokenAddress); + } + + /** + * @dev set sdex query address + * @param input bytes that acontains of: + * - protocol action code + * - new sdexQueryAddress + */ + function setSdexQueryAddress (bytes calldata input) internal { + (, address newSdexQueryAddress) = + abi.decode(input, (uint8, address)); + + emit SdexEvents.SdexQueryAddressSet(sovTokenAddress, newSdexQueryAddress); + setSdexQueryAddress(newSdexQueryAddress); + } } diff --git a/contracts/callpaths/LongPath.sol b/contracts/callpaths/LongPath.sol index 05b3876..080ba1e 100644 --- a/contracts/callpaths/LongPath.sol +++ b/contracts/callpaths/LongPath.sol @@ -6,11 +6,13 @@ import '../libraries/Directives.sol'; import '../libraries/Encoding.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/PriceGrid.sol'; +import '../libraries/ProtocolCmd.sol'; import '../mixins/MarketSequencer.sol'; import '../mixins/SettleLayer.sol'; import '../mixins/PoolRegistry.sol'; import '../mixins/ProtocolAccount.sol'; import '../mixins/StorageLayout.sol'; +import '../SdexEvents.sol'; /* @title Long path callpath sidecar. * @notice Defines a proxy sidecar contract that's used to move code outside the @@ -27,6 +29,20 @@ contract LongPath is MarketSequencer, SettleLayer, ProtocolAccount { using TokenFlow for TokenFlow.PairSeq; using CurveMath for CurveMath.CurveState; using Chaining for Chaining.PairFlow; + using ProtocolCmd for bytes; + + /* @notice Consolidated method for protocol control related commands. */ + function protocolCmd (bytes calldata cmd) virtual public { + uint8 code = uint8(cmd[31]); + + if (code == ProtocolCmd.SET_DEFAULT_PATH_CONVERSION_CODE) { + setDefaultPathConversion(cmd); + } else { + revert("Invalid command"); + } + + emit SdexEvents.SdexColdProtocolCmd(cmd); + } /* @notice Executes the user-defined compound order, constitutiin an arbitrary * combination of mints, burns and swaps across an arbitrary set of pools @@ -110,4 +126,19 @@ contract LongPath is MarketSequencer, SettleLayer, ProtocolAccount { function acceptSdexProxyRole (address, uint16 slot) public pure returns (bool) { return slot == SdexSlots.LONG_PROXY_IDX; } + + /** + * @notice set the defaultPathConversion for internal swap + * @dev first index of the decoded defaultPath is the source token address. + * @dev last index of the decoded defaultPath is the destination token address. + */ + function setDefaultPathConversion (bytes calldata input) private { + (, address[] memory defaultPath) = abi.decode(input, (uint8, address[])); + address sourceTokenAddress = address(defaultPath[0]); + address destTokenAddress = address(defaultPath[defaultPath.length - 1]); + + emit SdexEvents.SetDefaultPathConversion(sourceTokenAddress, destTokenAddress, defaultPath); + + setDefaultPathConversion(sourceTokenAddress, destTokenAddress, defaultPath); + } } diff --git a/contracts/callpaths/SafeModePath.sol b/contracts/callpaths/SafeModePath.sol index c943b8c..654e6f8 100644 --- a/contracts/callpaths/SafeModePath.sol +++ b/contracts/callpaths/SafeModePath.sol @@ -15,7 +15,7 @@ contract SafeModePath is ColdPath { sudoCmd(cmd); } - function userCmd (bytes calldata) override public payable { + function userCmd (bytes calldata) override public payable returns(bytes memory) { revert("Emergency Safe Mode"); } diff --git a/contracts/interfaces/IFeeProtocolCollector.sol b/contracts/interfaces/IFeeProtocolCollector.sol new file mode 100644 index 0000000..486b98e --- /dev/null +++ b/contracts/interfaces/IFeeProtocolCollector.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.19; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IFeeProtocolCollector { + function transferTokens(address _token, uint96 _amount) external; +} diff --git a/contracts/interfaces/ISdexQuery.sol b/contracts/interfaces/ISdexQuery.sol new file mode 100644 index 0000000..149a7ba --- /dev/null +++ b/contracts/interfaces/ISdexQuery.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.19; + +import "../libraries/PoolSpecs.sol"; + +interface ISdexQuery { + function queryPoolParams (address base, address quote, uint256 poolIdx) external view returns (PoolSpecs.Pool memory pool); +} \ No newline at end of file diff --git a/contracts/lens/SdexQuery.sol b/contracts/lens/SdexQuery.sol index 6338659..700ec52 100644 --- a/contracts/lens/SdexQuery.sol +++ b/contracts/lens/SdexQuery.sol @@ -531,4 +531,47 @@ contract SdexQuery { uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.LP_TOKEN_DEPLOYER_SLOT); return address(uint160(val)); } + + /* @notice Queries wrapped native token address */ + function queryWrappedNativeTokenAddress () public view returns (address) { + uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.WRAPPED_NATIVE_TOKEN_SLOT); + return address(uint160(val)); + } + + /* @notice Queries sov token address */ + function querySovTokenAddress () public view returns (address) { + uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.SOV_TOKEN_SLOT); + return address(uint160(val)); + } + + /* @notice Queries sdex query address */ + function querySdexQueryAddress () public view returns (address) { + uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.SDEX_QUERY_SLOT); + return address(uint160(val)); + } + + + /* @notice Queries the current lp token beacon address */ + function queryDefaultPathConversion (address sourceTokenAddress, address destTokenAddress) public view returns (address[] memory) { + bytes32 outerSlot = keccak256(abi.encode(sourceTokenAddress, SdexSlots.DEFAULT_PATH_CONVERSION_SLOT)); // slot of the outer mapping + bytes32 innerSlot = keccak256(abi.encode(destTokenAddress, outerSlot)); // slot of the inner mapping + + // The slot of the array length + uint arrayLengthSlot = uint(innerSlot); + + uint arrayLength; + assembly { + arrayLength := sload(arrayLengthSlot) + } + + address[] memory result = new address[](arrayLength); + + for (uint i = 0; i < arrayLength; i++) { + uint elementSlot = uint(keccak256(abi.encode(arrayLengthSlot))) + i; + uint256 val = SdexSwapDex(dex_).readSlot(elementSlot); + result[i] = address(uint160(val)); + } + + return result; + } } diff --git a/contracts/libraries/BytesConcat.sol b/contracts/libraries/BytesConcat.sol new file mode 100644 index 0000000..85ef910 --- /dev/null +++ b/contracts/libraries/BytesConcat.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.19; + +library BytesConcat { + function concat(bytes memory a, bytes[] memory array) internal pure returns (bytes memory) { + uint totalLength = a.length; + + for (uint i = 0; i < array.length; i++) { + totalLength += array[i].length; + } + + bytes memory result = new bytes(totalLength); + uint k = 0; + + // Copy the first array + for (uint i = 0; i < a.length; i++) { + result[k++] = a[i]; + } + + // Copy each element of the array + for (uint i = 0; i < array.length; i++) { + bytes memory b = array[i]; + for (uint j = 0; j < b.length; j++) { + result[k++] = b[j]; + } + } + + return result; + } +} \ No newline at end of file diff --git a/contracts/libraries/ProtocolCmd.sol b/contracts/libraries/ProtocolCmd.sol index 2a2113b..bcc1980 100644 --- a/contracts/libraries/ProtocolCmd.sol +++ b/contracts/libraries/ProtocolCmd.sol @@ -54,6 +54,14 @@ library ProtocolCmd { uint8 constant SET_POOL_LP_TOKEN_CODE = 117; // Code to set LP token deployer contract address uint8 constant SET_LP_TOKEN_DEPLOYER_CODE = 118; + // Code to set Wrapped Native token contract address + uint8 constant SET_WRAPPED_NATIVE_TOKEN_CODE = 119; + // Code to set Sov token contract address + uint8 constant SET_SOV_TOKEN_CODE = 120; + // Code to set Sov token contract address + uint8 constant SET_SDEX_QUERY_CODE = 121; + // Code to set defaultPathConversion (path for swap) + uint8 constant SET_DEFAULT_PATH_CONVERSION_CODE = 122; //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/libraries/SdexConvertLib.sol b/contracts/libraries/SdexConvertLib.sol new file mode 100644 index 0000000..03528ac --- /dev/null +++ b/contracts/libraries/SdexConvertLib.sol @@ -0,0 +1,275 @@ +pragma solidity 0.8.19; + +import "./Directives.sol"; +import "../interfaces/ISdexQuery.sol"; +import "./PoolSpecs.sol"; +import "./BytesConcat.sol"; + +interface ISdexSwapDex { + function userCmd (uint16 callpath, bytes calldata cmd) external payable returns (bytes memory); + function swap (address base, address quote, + uint256 poolIdx, bool isBuy, bool inBaseQty, uint128 qty, uint16 tip, + uint128 limitPrice, uint128 minOut, + uint8 reserveFlags) external payable returns (int128 baseFlow, int128 quoteFlow); +} + +library SdexConvertLib { + using BytesConcat for bytes; + uint256 constant POOL_IDX = 410; + uint16 constant LONG_PROXY_IDX = 130; + + function swap (address sdexSwapDexAddress, address sdexQueryAddress, address sourceTokenAddress, address destTokenAddress, uint128 amount, bool useSurplus, address[] memory conversionPath) internal returns(int128 convertedDestinationAmount) { + /** If swap path is not defined, try to swap from direct pool */ + if(conversionPath.length == 0) { + return directPoolSwap( + sdexSwapDexAddress, + sdexQueryAddress, + sourceTokenAddress, + destTokenAddress, + amount, + useSurplus + ); + } else { + return conversionPathSwap( + sdexSwapDexAddress, + amount, + useSurplus, + conversionPath + ); + } + } + + function directPoolSwap( + address sdexSwapDexAddress, + address sdexQueryAddress, + address sourceTokenAddress, + address destTokenAddress, + uint128 amount, + bool useSurplus + ) internal returns (int128) { + address base = sourceTokenAddress <= destTokenAddress ? sourceTokenAddress : destTokenAddress; + address quote = sourceTokenAddress <= destTokenAddress ? destTokenAddress : sourceTokenAddress; + + uint8 reserveFlags = useSurplus ? (sourceTokenAddress == base ? 1 : 2) : 0; + + PoolSpecs.Pool memory pool = ISdexQuery(sdexQueryAddress).queryPoolParams(base, quote, POOL_IDX); + + if (pool.schema_ == 0) revert("no direct pool and swap path are defined"); + + (int128 baseFlow, int128 quoteFlow) = ISdexSwapDex(sdexSwapDexAddress).swap( + base, + quote, + POOL_IDX, + true, + true, + amount, + 0, + 0, + 2**126, + reserveFlags + ); + + return sourceTokenAddress <= destTokenAddress ? quoteFlow : baseFlow; + } + + function conversionPathSwap( + address sdexSwapDexAddress, + uint128 amount, + bool useSurplus, + address[] memory conversionPath + ) internal returns (int128) { + require(conversionPath.length < 2, "invalid conversion path length"); + + address base0 = conversionPath[0] <= conversionPath[1] ? conversionPath[0] : conversionPath[1]; + address quote0 = conversionPath[0] <= conversionPath[1] ? conversionPath[1] : conversionPath[0]; + Directives.SettlementChannel memory settlementChannel = Directives.SettlementChannel({ + token_: base0, + limitQty_: 0, + dustThresh_: 0, + useSurplus_: useSurplus + }); + + Directives.HopDirective[] memory hops = new Directives.HopDirective[](1 + ((conversionPath.length - 2) * 2)); + Directives.PoolDirective[] memory pools = new Directives.PoolDirective[](1); + pools[1] = Directives.PoolDirective({ + poolIdx_: POOL_IDX, + ambient_: Directives.AmbientDirective({ + isAdd_: true, + rollType_: 0, + liquidity_: 0 + }), + conc_: new Directives.ConcentratedDirective[](0), + swap_: Directives.SwapDirective({ + isBuy_: true, + inBaseQty_: true, + rollType_: 0, + qty_: amount, + limitPrice_: 0 + }), + chain_: Directives.ChainingFlags({ + rollExit_: false, + swapDefer_: false, + offsetSurplus_: false + }) + }); + + hops[0] = Directives.HopDirective({ + pools_: pools, + settle_: Directives.SettlementChannel({ + token_: quote0, + limitQty_: 0, + dustThresh_: 0, + useSurplus_: useSurplus + }), + improve_: Directives.PriceImproveReq({ + isEnabled_: false, + useBaseSide_: false + }) + }); + + uint256 increment = 0; + for(uint256 i = 1; i < conversionPath.length - 1; i++) { + address baseToken = conversionPath[i] <= conversionPath[i+1] ? conversionPath[i] : conversionPath[i+1]; + address quoteToken = conversionPath[i] <= conversionPath[i] ? conversionPath[i+1] : conversionPath[i]; + + hops[i+increment] = Directives.HopDirective({ + pools_: new Directives.PoolDirective[](0), + settle_: Directives.SettlementChannel({ + token_: baseToken, + limitQty_: 0, + dustThresh_: 0, + useSurplus_: useSurplus + }), + improve_: Directives.PriceImproveReq({ + isEnabled_: false, + useBaseSide_: false + }) + }); + + hops[i+increment+1] = Directives.HopDirective({ + pools_: new Directives.PoolDirective[](0), + settle_: Directives.SettlementChannel({ + token_: quoteToken, + limitQty_: 0, + dustThresh_: 0, + useSurplus_: useSurplus + }), + improve_: Directives.PriceImproveReq({ + isEnabled_: false, + useBaseSide_: false + }) + }); + + increment++; + } + + bytes memory cmd = abi.encodeWithSelector(ISdexSwapDex.userCmd.selector, LONG_PROXY_IDX, encodeBytes(Directives.OrderDirective({ + open_: settlementChannel, + hops_: hops + }))); + + /** call the long path usercmd */ + bytes memory longSwapResultData = ISdexSwapDex(sdexSwapDexAddress).userCmd(LONG_PROXY_IDX, cmd); + int128[] memory decodedLongSwapResultData = abi.decode(longSwapResultData, (int128[])); + + return conversionPath[conversionPath.length - 1] <= conversionPath[conversionPath.length - 2] ? decodedLongSwapResultData[decodedLongSwapResultData.length - 3] : decodedLongSwapResultData[decodedLongSwapResultData.length - 2]; + } + + + function encodeBytes(Directives.OrderDirective memory order) internal view returns (bytes memory) { + bytes memory schema = abi.encodePacked(uint256(1)); // LONG_FORM_SCHEMA_TYPE + bytes memory openBytes = encodeSettlement(order.open_); + bytes memory hopsBytes = encodeHop(order.hops_); + + return bytes.concat(schema, openBytes, hopsBytes); + } + + function encodeSettlement(Directives.SettlementChannel memory dir) internal pure returns (bytes memory) { + return abi.encodePacked( + dir.token_, + dir.limitQty_, + dir.dustThresh_, + dir.useSurplus_ + ); + } + + function encodeHop(Directives.HopDirective[] memory hops) internal pure returns(bytes memory) { + bytes[] memory hopsBytes = new bytes[](hops.length); + for(uint256 i = 0; i < hops.length; i++) { + Directives.HopDirective memory hop = hops[i]; + bytes memory poolsBytes = encodePool(hop.pools_); + bytes memory settleBytes = encodeSettlement(hop.settle_); + bytes memory improveBytes = encodeImprove(hop.improve_); + hopsBytes[i] = (abi.encode(poolsBytes, settleBytes, improveBytes)); + } + return abi.encode(hops.length).concat(hopsBytes); + } + + function encodeHop(Directives.HopDirective memory hop) internal pure returns (bytes memory) { + bytes memory pools = encodePool(hop.pools_); + bytes memory settle = encodeSettlement(hop.settle_); + bytes memory improve = encodeImprove(hop.improve_); + + return bytes.concat(pools, settle, improve); + } + + function encodePool(Directives.PoolDirective[] memory pools) internal pure returns (bytes memory) { + bytes[] memory poolsBytes = new bytes[](pools.length); + for(uint256 i = 0; i < pools.length; i++) { + Directives.PoolDirective memory pool = pools[i]; + bytes memory poolIdxBytes = abi.encode(pool.poolIdx_); + bytes memory passiveBytes = encodePassive(pool.ambient_, pool.conc_); + bytes memory swapBytes = encodeSwap(pool.swap_); + bytes memory chainBytes = encodeChain(pool.chain_); + + poolsBytes[i] = (abi.encode(poolIdxBytes, passiveBytes, swapBytes, chainBytes)); + } + + return abi.encode(pools.length).concat(poolsBytes); + } + + function encodeChain(Directives.ChainingFlags memory chainFlags) internal pure returns (bytes memory) { + return abi.encode(chainFlags.rollExit_, chainFlags.swapDefer_, chainFlags.offsetSurplus_); + } + + function encodeSwap(Directives.SwapDirective memory swapDirective) internal pure returns (bytes memory) { + return abi.encode( + swapDirective.isBuy_, + swapDirective.inBaseQty_, + swapDirective.rollType_, + swapDirective.rollType_, + swapDirective.qty_, + swapDirective.limitPrice_ + ); + } + + function encodePassive(Directives.AmbientDirective memory ambientDir, Directives.ConcentratedDirective[] memory concDir) internal pure returns (bytes memory) { + bytes memory ambAdd = abi.encode(ambientDir.isAdd_); + bytes memory rollType = abi.encode(ambientDir.rollType_); + bytes memory ambLiq = abi.encode(ambientDir.liquidity_); + bytes memory conc = encodeConcentrated(concDir); + + return bytes.concat(ambAdd, rollType, ambLiq, conc); + } + + function encodeConcentrated(Directives.ConcentratedDirective[] memory concs) internal pure returns (bytes memory) { + bytes[] memory concsBytes = new bytes[](concs.length); + for(uint256 i = 0; i < concs.length; i++) { + Directives.ConcentratedDirective memory conc = concs[i]; + bytes memory openTick = abi.encode(conc.lowTick_); + bytes memory closeTick = abi.encode(conc.highTick_); + bytes memory isRelTick = abi.encode(conc.isTickRel_); + bytes memory isAdd = abi.encode(conc.isAdd_); + bytes memory rollType = abi.encode(conc.rollType_); + bytes memory liq = abi.encode(conc.liquidity_); + + concsBytes[i] = abi.encode(openTick, closeTick, isRelTick, isAdd, rollType, liq); + } + + return abi.encode(concs.length).concat(concsBytes); + } + + function encodeImprove(Directives.PriceImproveReq memory improve) internal pure returns (bytes memory) { + return abi.encode(improve.isEnabled_, improve.useBaseSide_); + } +} \ No newline at end of file diff --git a/contracts/mixins/PoolRegistry.sol b/contracts/mixins/PoolRegistry.sol index a8a0a2b..c7d4a5a 100644 --- a/contracts/mixins/PoolRegistry.sol +++ b/contracts/mixins/PoolRegistry.sol @@ -367,4 +367,48 @@ contract PoolRegistry is StorageLayout { require(newLpTokenDeployerAddress.isContract(), "Invalid lpToken deployer address"); lpTokenDeployerAddress = newLpTokenDeployerAddress; } + + /** + * @dev set wrapped native token address + * @param newWrappedNativeTokenAddress new wrapped native token address + */ + function setWrappedNativeTokenAddress (address newWrappedNativeTokenAddress) internal { + require(newWrappedNativeTokenAddress.isContract(), "Invalid new wrapped native token address"); + wrappedNativeTokenAddress = newWrappedNativeTokenAddress; + } + + /** + * @dev set sov token address + * @param newSovTokenAddress new sov token address + */ + function setSovTokenAddress (address newSovTokenAddress) internal { + require(newSovTokenAddress.isContract(), "Invalid new sov token address"); + sovTokenAddress = newSovTokenAddress; + } + + /** + * @dev set sdex query address + * @param newSdexQueryAddress new sdex query address + */ + function setSdexQueryAddress (address newSdexQueryAddress) internal { + require(newSdexQueryAddress.isContract(), "Invalid sdex query address"); + sdexQueryAddress = newSdexQueryAddress; + } + + /** + * @dev set defaultPathConversion + * @param sourceTokenAddress source token address. + * @param destTokenAddress destination token address. + * @param defaultPath default route paths for conversion. + */ + function setDefaultPathConversion (address sourceTokenAddress, address destTokenAddress, address[] memory defaultPath) internal { + uint256 defaultPathLength = defaultPath.length; + require(defaultPathLength >= 3, "ERR_PATH_LENGTH"); + + for (uint256 i = 0; i < defaultPathLength; i++) { + require(Address.isContract(address(defaultPath[i])), "ERR_PATH_NON_CONTRACT_ADDR"); + } + + defaultPathConversion[sourceTokenAddress][destTokenAddress] = defaultPath; + } } diff --git a/contracts/mixins/ProtocolAccount.sol b/contracts/mixins/ProtocolAccount.sol index 0c85f4b..47fd29d 100644 --- a/contracts/mixins/ProtocolAccount.sol +++ b/contracts/mixins/ProtocolAccount.sol @@ -6,6 +6,9 @@ import '../libraries/TransferHelper.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/SafeCast.sol'; import './StorageLayout.sol'; +import '../libraries/SdexConvertLib.sol'; + +import '../interfaces/IFeeProtocolCollector.sol'; /* @title Protocol Account Mixin * @notice Tracks and pays out the accumulated protocol fees across the entire exchange @@ -36,13 +39,35 @@ contract ProtocolAccount is StorageLayout { /* @notice Pays out the earned, but unclaimed protocol fees in the pool. * @param recv - The receiver of the protocol fees. - * @param token - The token address of the quote token. */ - function disburseProtocolFees (address recv, address token) internal { + * @param token - The token address of the quote token. + * @return converted amount to wrapped native token */ + function disburseProtocolFees (address recv, address token) internal returns (uint256 convertedAmountToWrappedNativeToken) { uint128 collected = feesAccum_[token]; feesAccum_[token] = 0; if (collected > 0) { - bytes32 payoutKey = keccak256(abi.encode(recv, token)); + /** if sov token address: + - no need to Convert token + - directly deposit sov to fee protocol collector + */ + bytes32 payoutKey = keccak256(abi.encode(address(this), token)); userBals_[payoutKey].surplusCollateral_ += collected; + if(token == sovTokenAddress) { + require(userBals_[payoutKey].surplusCollateral_ <= type(uint96).max, "Value exceeds uint96 range"); + IFeeProtocolCollector(treasury_).transferTokens(token, uint96(userBals_[payoutKey].surplusCollateral_)); + userBals_[payoutKey].surplusCollateral_ = 0; + } else { + /** for non sov, set the surplusCollateral for this protocol contract, and convert the token to the wrapped native token */ + int256 rawConvertedAmount = SdexConvertLib.swap(address(this), sdexQueryAddress, token, wrappedNativeTokenAddress, userBals_[payoutKey].surplusCollateral_, true, defaultPathConversion[token][wrappedNativeTokenAddress]); + + /** By right, the converted amount (flow amount) that is returned should be negative, which indicates that the pool is paying user */ + require(rawConvertedAmount >= 0, "Invalid conversion amount result"); + + convertedAmountToWrappedNativeToken = uint256(rawConvertedAmount * -1); + + /** Transfer the converted wrapped native token to fee collector */ + TransferHelper.safeTransfer(wrappedNativeTokenAddress, recv, convertedAmountToWrappedNativeToken); + userBals_[payoutKey].surplusCollateral_ = 0; + } } } } diff --git a/contracts/mixins/StorageLayout.sol b/contracts/mixins/StorageLayout.sol index 450cee6..19a70fc 100644 --- a/contracts/mixins/StorageLayout.sol +++ b/contracts/mixins/StorageLayout.sol @@ -168,6 +168,19 @@ contract StorageLayout { mapping(bytes32 => address) internal poolLpTokens; address lpTokenDeployerAddress; /**************************************************************/ + + /**************************************************************/ + // Swap Default Path Conversion + /**************************************************************/ + + /// @dev Defines the defaultPath of conversion swap. This is created to prevent the non-nativeToken pairs returning the shortest path which will not give the best rate. + /// Will be used in internal swap. + address wrappedNativeTokenAddress; + address sovTokenAddress; + address sdexQueryAddress; + mapping(address => mapping(address => address[])) internal defaultPathConversion; + + /**************************************************************/ } /* @notice Contains the storage or storage hash offsets of the fields and sidecars @@ -196,6 +209,10 @@ library SdexSlots { uint constant public POS_MAP_SLOT_72 = 65554; uint constant public POOL_LP_TOKEN_SLOT = 65555; uint constant public LP_TOKEN_DEPLOYER_SLOT = 65556; + uint constant public WRAPPED_NATIVE_TOKEN_SLOT = 65557; + uint constant public SOV_TOKEN_SLOT = 65558; + uint constant public SDEX_QUERY_SLOT = 65559; + uint constant public DEFAULT_PATH_CONVERSION_SLOT = 65560; // The slots of the currently attached sidecar proxy contracts. These are set by diff --git a/contracts/test/TestProtocolAcct.sol b/contracts/test/TestProtocolAcct.sol index cd8d7a5..b846e77 100644 --- a/contracts/test/TestProtocolAcct.sol +++ b/contracts/test/TestProtocolAcct.sol @@ -32,8 +32,8 @@ contract TestProtocolAccount is ProtocolAccount { return feesAccum_[token]; } - function disburseProtocol (address recv, address token) public { - disburseProtocolFees(recv, token); + function disburseProtocol (address recv, address token) public returns (uint256) { + return disburseProtocolFees(recv, token); } function getPaidFees (address recv, address token) public view returns (uint128) { diff --git a/test/FacadePool.ts b/test/FacadePool.ts index 5face04..ead6c1e 100644 --- a/test/FacadePool.ts +++ b/test/FacadePool.ts @@ -638,6 +638,12 @@ export class TestPool { await (await this.dex).connect(await this.auth).protocolCmd(this.COLD_PROXY, takeCmd, false) } + async setDefaultPathConversion (conversionPath: string[]) { + let abiCoder = new ethers.utils.AbiCoder() + let takeCmd = abiCoder.encode(["uint8", "address[]"], [122, conversionPath]); + await (await this.dex).connect(await this.auth).protocolCmd(this.LONG_PROXY, takeCmd, false) + } + async testRevisePoolIdx (idx: number, feeRate: number, protoTake: number, tickSize:number, jit: number = 0): Promise { let abiCoder = new ethers.utils.AbiCoder() From 187c965da5c109ee500096977be9e993615de336 Mon Sep 17 00:00:00 2001 From: cwsnt Date: Tue, 4 Jun 2024 09:04:37 +0700 Subject: [PATCH 3/5] add readme for on going lib --- contracts/libraries/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contracts/libraries/README.md diff --git a/contracts/libraries/README.md b/contracts/libraries/README.md new file mode 100644 index 0000000..17c6d13 --- /dev/null +++ b/contracts/libraries/README.md @@ -0,0 +1,3 @@ +# SdexConvertLib +This SdexConvertLib is meant to be used for inside & outside integration to do the swap to this Sdex protocol (which support both direct & multihop swap) +This libraray is not 100% ready to be used, since it's still in progress and not fully tested & covered by unit tests. \ No newline at end of file From 97b3b402e03bcd73b47789ae3997f8ceffebaa63 Mon Sep 17 00:00:00 2001 From: cwsnt Date: Wed, 12 Jun 2024 21:37:25 +0700 Subject: [PATCH 4/5] init: collect protocol fee with direct transfer --- contracts/SdexEvents.sol | 15 -- contracts/callpaths/ColdPath.sol | 57 +---- contracts/callpaths/LongPath.sol | 30 --- contracts/callpaths/SafeModePath.sol | 2 +- contracts/lens/SdexQuery.sol | 43 ---- contracts/libraries/BytesConcat.sol | 30 --- contracts/libraries/ProtocolCmd.sol | 8 - contracts/libraries/SdexConvertLib.sol | 275 ------------------------- contracts/mixins/PoolRegistry.sol | 44 ---- contracts/mixins/ProtocolAccount.sol | 32 +-- contracts/mixins/StorageLayout.sol | 17 -- contracts/test/TestProtocolAcct.sol | 4 +- test/FacadePool.ts | 6 - 13 files changed, 15 insertions(+), 548 deletions(-) delete mode 100644 contracts/libraries/BytesConcat.sol delete mode 100644 contracts/libraries/SdexConvertLib.sol diff --git a/contracts/SdexEvents.sol b/contracts/SdexEvents.sol index 5a1ed42..184921b 100644 --- a/contracts/SdexEvents.sol +++ b/contracts/SdexEvents.sol @@ -98,19 +98,4 @@ library SdexEvents { /** LP Token */ event PoolLpTokenSet (address indexed base, address indexed quote, uint256 poolIdx, address indexed lpToken); event LpTokenDeployerSet (address indexed oldLpTokenDeployerAddress, address indexed newLpTokenDeployerAddress); - event WrappedNativeTokenAddressSet (address indexed oldWrappedNativeTokenAddress, address indexed newWrappedNativeTokenAddress); - event SovTokenAddressSet (address indexed oldSovTokenAddress, address indexed newSovTokenAddress); - event SdexQueryAddressSet (address indexed oldSdexQueryAddress, address indexed newSdexQueryAddress); - - /** Swap */ - /* @notice Emitted when a new defaultPathConversion is added / modified. - * @param sourceTokenAddress source of token address for swap. - * @param destTokenAddress target token address for swap. - * @param defaultPath route path for the swap. - */ - event SetDefaultPathConversion( - address indexed sourceTokenAddress, - address indexed destTokenAddress, - address[] defaultPath - ); } diff --git a/contracts/callpaths/ColdPath.sol b/contracts/callpaths/ColdPath.sol index 109cbd3..d14a289 100644 --- a/contracts/callpaths/ColdPath.sol +++ b/contracts/callpaths/ColdPath.sol @@ -64,12 +64,6 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { setPoolLpToken(cmd); } else if (code == ProtocolCmd.SET_LP_TOKEN_DEPLOYER_CODE) { setLpTokenDeployerAddress(cmd); - } else if (code == ProtocolCmd.SET_WRAPPED_NATIVE_TOKEN_CODE) { - setWrappedNativeTokenAddress(cmd); - } else if (code == ProtocolCmd.SET_SOV_TOKEN_CODE) { - setSovTokenAddress(cmd); - } else if (code == ProtocolCmd.SET_SDEX_QUERY_CODE) { - setSovTokenAddress(cmd); } else { sudoCmd(cmd); } @@ -97,7 +91,7 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { } - function userCmd (bytes calldata cmd) virtual public payable returns(bytes memory) { + function userCmd (bytes calldata cmd) virtual public payable { uint8 cmdCode = uint8(cmd[31]); if (cmdCode == UserCmd.INIT_POOL_CODE) { @@ -121,8 +115,7 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { } else if (cmdCode == UserCmd.GATE_ORACLE_COND) { checkGateOracle(cmd); } else if (cmdCode == UserCmd.COLLECT_TREASURY_CODE) { - uint256 convertedAmountToWrappedNativeToken = collectProtocol(cmd); - return abi.encodePacked(convertedAmountToWrappedNativeToken); + collectProtocol(cmd); } else { revert("Invalid command"); } @@ -255,12 +248,12 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { /* @notice Pays out the the protocol fees. * @param token The token for which the accumulated fees are being paid out. * (Or if 0x0 pays out native Ethereum.) */ - function collectProtocol (bytes calldata cmd) private onlyTreasury returns(uint256) { + function collectProtocol (bytes calldata cmd) private onlyTreasury { (, address token) = abi.decode(cmd, (uint8, address)); require(block.timestamp >= treasuryStartTime_, "Treasury start"); emit SdexEvents.ProtocolDividend(token, treasury_); - return disburseProtocolFees(treasury_, token); + disburseProtocolFees(treasury_, token); } /* @notice Sets the treasury address to receive protocol fees. Once set, the treasury cannot @@ -392,47 +385,5 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { emit SdexEvents.LpTokenDeployerSet(lpTokenDeployerAddress, newLpTokenDeployerAddress); setLpTokenDeployerAddress(newLpTokenDeployerAddress); } - - /** - * @dev set wrapped native token address - * @param input bytes that acontains of: - * - protocol action code - * - new wrappedNativeTokenAddress - */ - function setWrappedNativeTokenAddress (bytes calldata input) internal { - (, address newWrappedNativeTokenAddress) = - abi.decode(input, (uint8, address)); - - emit SdexEvents.WrappedNativeTokenAddressSet(wrappedNativeTokenAddress, newWrappedNativeTokenAddress); - setWrappedNativeTokenAddress(newWrappedNativeTokenAddress); - } - - /** - * @dev set sov token address - * @param input bytes that acontains of: - * - protocol action code - * - new sovTokenAddress - */ - function setSovTokenAddress (bytes calldata input) internal { - (, address newSovTokenAddress) = - abi.decode(input, (uint8, address)); - - emit SdexEvents.SovTokenAddressSet(sovTokenAddress, newSovTokenAddress); - setSovTokenAddress(newSovTokenAddress); - } - - /** - * @dev set sdex query address - * @param input bytes that acontains of: - * - protocol action code - * - new sdexQueryAddress - */ - function setSdexQueryAddress (bytes calldata input) internal { - (, address newSdexQueryAddress) = - abi.decode(input, (uint8, address)); - - emit SdexEvents.SdexQueryAddressSet(sovTokenAddress, newSdexQueryAddress); - setSdexQueryAddress(newSdexQueryAddress); - } } diff --git a/contracts/callpaths/LongPath.sol b/contracts/callpaths/LongPath.sol index 080ba1e..c477efa 100644 --- a/contracts/callpaths/LongPath.sol +++ b/contracts/callpaths/LongPath.sol @@ -12,7 +12,6 @@ import '../mixins/SettleLayer.sol'; import '../mixins/PoolRegistry.sol'; import '../mixins/ProtocolAccount.sol'; import '../mixins/StorageLayout.sol'; -import '../SdexEvents.sol'; /* @title Long path callpath sidecar. * @notice Defines a proxy sidecar contract that's used to move code outside the @@ -29,20 +28,6 @@ contract LongPath is MarketSequencer, SettleLayer, ProtocolAccount { using TokenFlow for TokenFlow.PairSeq; using CurveMath for CurveMath.CurveState; using Chaining for Chaining.PairFlow; - using ProtocolCmd for bytes; - - /* @notice Consolidated method for protocol control related commands. */ - function protocolCmd (bytes calldata cmd) virtual public { - uint8 code = uint8(cmd[31]); - - if (code == ProtocolCmd.SET_DEFAULT_PATH_CONVERSION_CODE) { - setDefaultPathConversion(cmd); - } else { - revert("Invalid command"); - } - - emit SdexEvents.SdexColdProtocolCmd(cmd); - } /* @notice Executes the user-defined compound order, constitutiin an arbitrary * combination of mints, burns and swaps across an arbitrary set of pools @@ -126,19 +111,4 @@ contract LongPath is MarketSequencer, SettleLayer, ProtocolAccount { function acceptSdexProxyRole (address, uint16 slot) public pure returns (bool) { return slot == SdexSlots.LONG_PROXY_IDX; } - - /** - * @notice set the defaultPathConversion for internal swap - * @dev first index of the decoded defaultPath is the source token address. - * @dev last index of the decoded defaultPath is the destination token address. - */ - function setDefaultPathConversion (bytes calldata input) private { - (, address[] memory defaultPath) = abi.decode(input, (uint8, address[])); - address sourceTokenAddress = address(defaultPath[0]); - address destTokenAddress = address(defaultPath[defaultPath.length - 1]); - - emit SdexEvents.SetDefaultPathConversion(sourceTokenAddress, destTokenAddress, defaultPath); - - setDefaultPathConversion(sourceTokenAddress, destTokenAddress, defaultPath); - } } diff --git a/contracts/callpaths/SafeModePath.sol b/contracts/callpaths/SafeModePath.sol index 654e6f8..c943b8c 100644 --- a/contracts/callpaths/SafeModePath.sol +++ b/contracts/callpaths/SafeModePath.sol @@ -15,7 +15,7 @@ contract SafeModePath is ColdPath { sudoCmd(cmd); } - function userCmd (bytes calldata) override public payable returns(bytes memory) { + function userCmd (bytes calldata) override public payable { revert("Emergency Safe Mode"); } diff --git a/contracts/lens/SdexQuery.sol b/contracts/lens/SdexQuery.sol index 700ec52..6338659 100644 --- a/contracts/lens/SdexQuery.sol +++ b/contracts/lens/SdexQuery.sol @@ -531,47 +531,4 @@ contract SdexQuery { uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.LP_TOKEN_DEPLOYER_SLOT); return address(uint160(val)); } - - /* @notice Queries wrapped native token address */ - function queryWrappedNativeTokenAddress () public view returns (address) { - uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.WRAPPED_NATIVE_TOKEN_SLOT); - return address(uint160(val)); - } - - /* @notice Queries sov token address */ - function querySovTokenAddress () public view returns (address) { - uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.SOV_TOKEN_SLOT); - return address(uint160(val)); - } - - /* @notice Queries sdex query address */ - function querySdexQueryAddress () public view returns (address) { - uint256 val = SdexSwapDex(dex_).readSlot(SdexSlots.SDEX_QUERY_SLOT); - return address(uint160(val)); - } - - - /* @notice Queries the current lp token beacon address */ - function queryDefaultPathConversion (address sourceTokenAddress, address destTokenAddress) public view returns (address[] memory) { - bytes32 outerSlot = keccak256(abi.encode(sourceTokenAddress, SdexSlots.DEFAULT_PATH_CONVERSION_SLOT)); // slot of the outer mapping - bytes32 innerSlot = keccak256(abi.encode(destTokenAddress, outerSlot)); // slot of the inner mapping - - // The slot of the array length - uint arrayLengthSlot = uint(innerSlot); - - uint arrayLength; - assembly { - arrayLength := sload(arrayLengthSlot) - } - - address[] memory result = new address[](arrayLength); - - for (uint i = 0; i < arrayLength; i++) { - uint elementSlot = uint(keccak256(abi.encode(arrayLengthSlot))) + i; - uint256 val = SdexSwapDex(dex_).readSlot(elementSlot); - result[i] = address(uint160(val)); - } - - return result; - } } diff --git a/contracts/libraries/BytesConcat.sol b/contracts/libraries/BytesConcat.sol deleted file mode 100644 index 85ef910..0000000 --- a/contracts/libraries/BytesConcat.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.19; - -library BytesConcat { - function concat(bytes memory a, bytes[] memory array) internal pure returns (bytes memory) { - uint totalLength = a.length; - - for (uint i = 0; i < array.length; i++) { - totalLength += array[i].length; - } - - bytes memory result = new bytes(totalLength); - uint k = 0; - - // Copy the first array - for (uint i = 0; i < a.length; i++) { - result[k++] = a[i]; - } - - // Copy each element of the array - for (uint i = 0; i < array.length; i++) { - bytes memory b = array[i]; - for (uint j = 0; j < b.length; j++) { - result[k++] = b[j]; - } - } - - return result; - } -} \ No newline at end of file diff --git a/contracts/libraries/ProtocolCmd.sol b/contracts/libraries/ProtocolCmd.sol index bcc1980..2a2113b 100644 --- a/contracts/libraries/ProtocolCmd.sol +++ b/contracts/libraries/ProtocolCmd.sol @@ -54,14 +54,6 @@ library ProtocolCmd { uint8 constant SET_POOL_LP_TOKEN_CODE = 117; // Code to set LP token deployer contract address uint8 constant SET_LP_TOKEN_DEPLOYER_CODE = 118; - // Code to set Wrapped Native token contract address - uint8 constant SET_WRAPPED_NATIVE_TOKEN_CODE = 119; - // Code to set Sov token contract address - uint8 constant SET_SOV_TOKEN_CODE = 120; - // Code to set Sov token contract address - uint8 constant SET_SDEX_QUERY_CODE = 121; - // Code to set defaultPathConversion (path for swap) - uint8 constant SET_DEFAULT_PATH_CONVERSION_CODE = 122; //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/libraries/SdexConvertLib.sol b/contracts/libraries/SdexConvertLib.sol deleted file mode 100644 index 03528ac..0000000 --- a/contracts/libraries/SdexConvertLib.sol +++ /dev/null @@ -1,275 +0,0 @@ -pragma solidity 0.8.19; - -import "./Directives.sol"; -import "../interfaces/ISdexQuery.sol"; -import "./PoolSpecs.sol"; -import "./BytesConcat.sol"; - -interface ISdexSwapDex { - function userCmd (uint16 callpath, bytes calldata cmd) external payable returns (bytes memory); - function swap (address base, address quote, - uint256 poolIdx, bool isBuy, bool inBaseQty, uint128 qty, uint16 tip, - uint128 limitPrice, uint128 minOut, - uint8 reserveFlags) external payable returns (int128 baseFlow, int128 quoteFlow); -} - -library SdexConvertLib { - using BytesConcat for bytes; - uint256 constant POOL_IDX = 410; - uint16 constant LONG_PROXY_IDX = 130; - - function swap (address sdexSwapDexAddress, address sdexQueryAddress, address sourceTokenAddress, address destTokenAddress, uint128 amount, bool useSurplus, address[] memory conversionPath) internal returns(int128 convertedDestinationAmount) { - /** If swap path is not defined, try to swap from direct pool */ - if(conversionPath.length == 0) { - return directPoolSwap( - sdexSwapDexAddress, - sdexQueryAddress, - sourceTokenAddress, - destTokenAddress, - amount, - useSurplus - ); - } else { - return conversionPathSwap( - sdexSwapDexAddress, - amount, - useSurplus, - conversionPath - ); - } - } - - function directPoolSwap( - address sdexSwapDexAddress, - address sdexQueryAddress, - address sourceTokenAddress, - address destTokenAddress, - uint128 amount, - bool useSurplus - ) internal returns (int128) { - address base = sourceTokenAddress <= destTokenAddress ? sourceTokenAddress : destTokenAddress; - address quote = sourceTokenAddress <= destTokenAddress ? destTokenAddress : sourceTokenAddress; - - uint8 reserveFlags = useSurplus ? (sourceTokenAddress == base ? 1 : 2) : 0; - - PoolSpecs.Pool memory pool = ISdexQuery(sdexQueryAddress).queryPoolParams(base, quote, POOL_IDX); - - if (pool.schema_ == 0) revert("no direct pool and swap path are defined"); - - (int128 baseFlow, int128 quoteFlow) = ISdexSwapDex(sdexSwapDexAddress).swap( - base, - quote, - POOL_IDX, - true, - true, - amount, - 0, - 0, - 2**126, - reserveFlags - ); - - return sourceTokenAddress <= destTokenAddress ? quoteFlow : baseFlow; - } - - function conversionPathSwap( - address sdexSwapDexAddress, - uint128 amount, - bool useSurplus, - address[] memory conversionPath - ) internal returns (int128) { - require(conversionPath.length < 2, "invalid conversion path length"); - - address base0 = conversionPath[0] <= conversionPath[1] ? conversionPath[0] : conversionPath[1]; - address quote0 = conversionPath[0] <= conversionPath[1] ? conversionPath[1] : conversionPath[0]; - Directives.SettlementChannel memory settlementChannel = Directives.SettlementChannel({ - token_: base0, - limitQty_: 0, - dustThresh_: 0, - useSurplus_: useSurplus - }); - - Directives.HopDirective[] memory hops = new Directives.HopDirective[](1 + ((conversionPath.length - 2) * 2)); - Directives.PoolDirective[] memory pools = new Directives.PoolDirective[](1); - pools[1] = Directives.PoolDirective({ - poolIdx_: POOL_IDX, - ambient_: Directives.AmbientDirective({ - isAdd_: true, - rollType_: 0, - liquidity_: 0 - }), - conc_: new Directives.ConcentratedDirective[](0), - swap_: Directives.SwapDirective({ - isBuy_: true, - inBaseQty_: true, - rollType_: 0, - qty_: amount, - limitPrice_: 0 - }), - chain_: Directives.ChainingFlags({ - rollExit_: false, - swapDefer_: false, - offsetSurplus_: false - }) - }); - - hops[0] = Directives.HopDirective({ - pools_: pools, - settle_: Directives.SettlementChannel({ - token_: quote0, - limitQty_: 0, - dustThresh_: 0, - useSurplus_: useSurplus - }), - improve_: Directives.PriceImproveReq({ - isEnabled_: false, - useBaseSide_: false - }) - }); - - uint256 increment = 0; - for(uint256 i = 1; i < conversionPath.length - 1; i++) { - address baseToken = conversionPath[i] <= conversionPath[i+1] ? conversionPath[i] : conversionPath[i+1]; - address quoteToken = conversionPath[i] <= conversionPath[i] ? conversionPath[i+1] : conversionPath[i]; - - hops[i+increment] = Directives.HopDirective({ - pools_: new Directives.PoolDirective[](0), - settle_: Directives.SettlementChannel({ - token_: baseToken, - limitQty_: 0, - dustThresh_: 0, - useSurplus_: useSurplus - }), - improve_: Directives.PriceImproveReq({ - isEnabled_: false, - useBaseSide_: false - }) - }); - - hops[i+increment+1] = Directives.HopDirective({ - pools_: new Directives.PoolDirective[](0), - settle_: Directives.SettlementChannel({ - token_: quoteToken, - limitQty_: 0, - dustThresh_: 0, - useSurplus_: useSurplus - }), - improve_: Directives.PriceImproveReq({ - isEnabled_: false, - useBaseSide_: false - }) - }); - - increment++; - } - - bytes memory cmd = abi.encodeWithSelector(ISdexSwapDex.userCmd.selector, LONG_PROXY_IDX, encodeBytes(Directives.OrderDirective({ - open_: settlementChannel, - hops_: hops - }))); - - /** call the long path usercmd */ - bytes memory longSwapResultData = ISdexSwapDex(sdexSwapDexAddress).userCmd(LONG_PROXY_IDX, cmd); - int128[] memory decodedLongSwapResultData = abi.decode(longSwapResultData, (int128[])); - - return conversionPath[conversionPath.length - 1] <= conversionPath[conversionPath.length - 2] ? decodedLongSwapResultData[decodedLongSwapResultData.length - 3] : decodedLongSwapResultData[decodedLongSwapResultData.length - 2]; - } - - - function encodeBytes(Directives.OrderDirective memory order) internal view returns (bytes memory) { - bytes memory schema = abi.encodePacked(uint256(1)); // LONG_FORM_SCHEMA_TYPE - bytes memory openBytes = encodeSettlement(order.open_); - bytes memory hopsBytes = encodeHop(order.hops_); - - return bytes.concat(schema, openBytes, hopsBytes); - } - - function encodeSettlement(Directives.SettlementChannel memory dir) internal pure returns (bytes memory) { - return abi.encodePacked( - dir.token_, - dir.limitQty_, - dir.dustThresh_, - dir.useSurplus_ - ); - } - - function encodeHop(Directives.HopDirective[] memory hops) internal pure returns(bytes memory) { - bytes[] memory hopsBytes = new bytes[](hops.length); - for(uint256 i = 0; i < hops.length; i++) { - Directives.HopDirective memory hop = hops[i]; - bytes memory poolsBytes = encodePool(hop.pools_); - bytes memory settleBytes = encodeSettlement(hop.settle_); - bytes memory improveBytes = encodeImprove(hop.improve_); - hopsBytes[i] = (abi.encode(poolsBytes, settleBytes, improveBytes)); - } - return abi.encode(hops.length).concat(hopsBytes); - } - - function encodeHop(Directives.HopDirective memory hop) internal pure returns (bytes memory) { - bytes memory pools = encodePool(hop.pools_); - bytes memory settle = encodeSettlement(hop.settle_); - bytes memory improve = encodeImprove(hop.improve_); - - return bytes.concat(pools, settle, improve); - } - - function encodePool(Directives.PoolDirective[] memory pools) internal pure returns (bytes memory) { - bytes[] memory poolsBytes = new bytes[](pools.length); - for(uint256 i = 0; i < pools.length; i++) { - Directives.PoolDirective memory pool = pools[i]; - bytes memory poolIdxBytes = abi.encode(pool.poolIdx_); - bytes memory passiveBytes = encodePassive(pool.ambient_, pool.conc_); - bytes memory swapBytes = encodeSwap(pool.swap_); - bytes memory chainBytes = encodeChain(pool.chain_); - - poolsBytes[i] = (abi.encode(poolIdxBytes, passiveBytes, swapBytes, chainBytes)); - } - - return abi.encode(pools.length).concat(poolsBytes); - } - - function encodeChain(Directives.ChainingFlags memory chainFlags) internal pure returns (bytes memory) { - return abi.encode(chainFlags.rollExit_, chainFlags.swapDefer_, chainFlags.offsetSurplus_); - } - - function encodeSwap(Directives.SwapDirective memory swapDirective) internal pure returns (bytes memory) { - return abi.encode( - swapDirective.isBuy_, - swapDirective.inBaseQty_, - swapDirective.rollType_, - swapDirective.rollType_, - swapDirective.qty_, - swapDirective.limitPrice_ - ); - } - - function encodePassive(Directives.AmbientDirective memory ambientDir, Directives.ConcentratedDirective[] memory concDir) internal pure returns (bytes memory) { - bytes memory ambAdd = abi.encode(ambientDir.isAdd_); - bytes memory rollType = abi.encode(ambientDir.rollType_); - bytes memory ambLiq = abi.encode(ambientDir.liquidity_); - bytes memory conc = encodeConcentrated(concDir); - - return bytes.concat(ambAdd, rollType, ambLiq, conc); - } - - function encodeConcentrated(Directives.ConcentratedDirective[] memory concs) internal pure returns (bytes memory) { - bytes[] memory concsBytes = new bytes[](concs.length); - for(uint256 i = 0; i < concs.length; i++) { - Directives.ConcentratedDirective memory conc = concs[i]; - bytes memory openTick = abi.encode(conc.lowTick_); - bytes memory closeTick = abi.encode(conc.highTick_); - bytes memory isRelTick = abi.encode(conc.isTickRel_); - bytes memory isAdd = abi.encode(conc.isAdd_); - bytes memory rollType = abi.encode(conc.rollType_); - bytes memory liq = abi.encode(conc.liquidity_); - - concsBytes[i] = abi.encode(openTick, closeTick, isRelTick, isAdd, rollType, liq); - } - - return abi.encode(concs.length).concat(concsBytes); - } - - function encodeImprove(Directives.PriceImproveReq memory improve) internal pure returns (bytes memory) { - return abi.encode(improve.isEnabled_, improve.useBaseSide_); - } -} \ No newline at end of file diff --git a/contracts/mixins/PoolRegistry.sol b/contracts/mixins/PoolRegistry.sol index c7d4a5a..a8a0a2b 100644 --- a/contracts/mixins/PoolRegistry.sol +++ b/contracts/mixins/PoolRegistry.sol @@ -367,48 +367,4 @@ contract PoolRegistry is StorageLayout { require(newLpTokenDeployerAddress.isContract(), "Invalid lpToken deployer address"); lpTokenDeployerAddress = newLpTokenDeployerAddress; } - - /** - * @dev set wrapped native token address - * @param newWrappedNativeTokenAddress new wrapped native token address - */ - function setWrappedNativeTokenAddress (address newWrappedNativeTokenAddress) internal { - require(newWrappedNativeTokenAddress.isContract(), "Invalid new wrapped native token address"); - wrappedNativeTokenAddress = newWrappedNativeTokenAddress; - } - - /** - * @dev set sov token address - * @param newSovTokenAddress new sov token address - */ - function setSovTokenAddress (address newSovTokenAddress) internal { - require(newSovTokenAddress.isContract(), "Invalid new sov token address"); - sovTokenAddress = newSovTokenAddress; - } - - /** - * @dev set sdex query address - * @param newSdexQueryAddress new sdex query address - */ - function setSdexQueryAddress (address newSdexQueryAddress) internal { - require(newSdexQueryAddress.isContract(), "Invalid sdex query address"); - sdexQueryAddress = newSdexQueryAddress; - } - - /** - * @dev set defaultPathConversion - * @param sourceTokenAddress source token address. - * @param destTokenAddress destination token address. - * @param defaultPath default route paths for conversion. - */ - function setDefaultPathConversion (address sourceTokenAddress, address destTokenAddress, address[] memory defaultPath) internal { - uint256 defaultPathLength = defaultPath.length; - require(defaultPathLength >= 3, "ERR_PATH_LENGTH"); - - for (uint256 i = 0; i < defaultPathLength; i++) { - require(Address.isContract(address(defaultPath[i])), "ERR_PATH_NON_CONTRACT_ADDR"); - } - - defaultPathConversion[sourceTokenAddress][destTokenAddress] = defaultPath; - } } diff --git a/contracts/mixins/ProtocolAccount.sol b/contracts/mixins/ProtocolAccount.sol index 47fd29d..105ba66 100644 --- a/contracts/mixins/ProtocolAccount.sol +++ b/contracts/mixins/ProtocolAccount.sol @@ -6,7 +6,6 @@ import '../libraries/TransferHelper.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/SafeCast.sol'; import './StorageLayout.sol'; -import '../libraries/SdexConvertLib.sol'; import '../interfaces/IFeeProtocolCollector.sol'; @@ -40,34 +39,19 @@ contract ProtocolAccount is StorageLayout { /* @notice Pays out the earned, but unclaimed protocol fees in the pool. * @param recv - The receiver of the protocol fees. * @param token - The token address of the quote token. - * @return converted amount to wrapped native token */ - function disburseProtocolFees (address recv, address token) internal returns (uint256 convertedAmountToWrappedNativeToken) { + * @return withdrawn tokens */ + function disburseProtocolFees (address recv, address token) internal { uint128 collected = feesAccum_[token]; feesAccum_[token] = 0; if (collected > 0) { - /** if sov token address: - - no need to Convert token - - directly deposit sov to fee protocol collector + /** + * directly deposit token to fee protocol collector */ - bytes32 payoutKey = keccak256(abi.encode(address(this), token)); + bytes32 payoutKey = keccak256(abi.encode(recv, token)); userBals_[payoutKey].surplusCollateral_ += collected; - if(token == sovTokenAddress) { - require(userBals_[payoutKey].surplusCollateral_ <= type(uint96).max, "Value exceeds uint96 range"); - IFeeProtocolCollector(treasury_).transferTokens(token, uint96(userBals_[payoutKey].surplusCollateral_)); - userBals_[payoutKey].surplusCollateral_ = 0; - } else { - /** for non sov, set the surplusCollateral for this protocol contract, and convert the token to the wrapped native token */ - int256 rawConvertedAmount = SdexConvertLib.swap(address(this), sdexQueryAddress, token, wrappedNativeTokenAddress, userBals_[payoutKey].surplusCollateral_, true, defaultPathConversion[token][wrappedNativeTokenAddress]); - - /** By right, the converted amount (flow amount) that is returned should be negative, which indicates that the pool is paying user */ - require(rawConvertedAmount >= 0, "Invalid conversion amount result"); - - convertedAmountToWrappedNativeToken = uint256(rawConvertedAmount * -1); - - /** Transfer the converted wrapped native token to fee collector */ - TransferHelper.safeTransfer(wrappedNativeTokenAddress, recv, convertedAmountToWrappedNativeToken); - userBals_[payoutKey].surplusCollateral_ = 0; - } + require(userBals_[payoutKey].surplusCollateral_ <= type(uint96).max, "Value exceeds uint96 range"); + IFeeProtocolCollector(treasury_).transferTokens(token, uint96(userBals_[payoutKey].surplusCollateral_)); + userBals_[payoutKey].surplusCollateral_ = 0; } } } diff --git a/contracts/mixins/StorageLayout.sol b/contracts/mixins/StorageLayout.sol index 19a70fc..450cee6 100644 --- a/contracts/mixins/StorageLayout.sol +++ b/contracts/mixins/StorageLayout.sol @@ -168,19 +168,6 @@ contract StorageLayout { mapping(bytes32 => address) internal poolLpTokens; address lpTokenDeployerAddress; /**************************************************************/ - - /**************************************************************/ - // Swap Default Path Conversion - /**************************************************************/ - - /// @dev Defines the defaultPath of conversion swap. This is created to prevent the non-nativeToken pairs returning the shortest path which will not give the best rate. - /// Will be used in internal swap. - address wrappedNativeTokenAddress; - address sovTokenAddress; - address sdexQueryAddress; - mapping(address => mapping(address => address[])) internal defaultPathConversion; - - /**************************************************************/ } /* @notice Contains the storage or storage hash offsets of the fields and sidecars @@ -209,10 +196,6 @@ library SdexSlots { uint constant public POS_MAP_SLOT_72 = 65554; uint constant public POOL_LP_TOKEN_SLOT = 65555; uint constant public LP_TOKEN_DEPLOYER_SLOT = 65556; - uint constant public WRAPPED_NATIVE_TOKEN_SLOT = 65557; - uint constant public SOV_TOKEN_SLOT = 65558; - uint constant public SDEX_QUERY_SLOT = 65559; - uint constant public DEFAULT_PATH_CONVERSION_SLOT = 65560; // The slots of the currently attached sidecar proxy contracts. These are set by diff --git a/contracts/test/TestProtocolAcct.sol b/contracts/test/TestProtocolAcct.sol index b846e77..cd8d7a5 100644 --- a/contracts/test/TestProtocolAcct.sol +++ b/contracts/test/TestProtocolAcct.sol @@ -32,8 +32,8 @@ contract TestProtocolAccount is ProtocolAccount { return feesAccum_[token]; } - function disburseProtocol (address recv, address token) public returns (uint256) { - return disburseProtocolFees(recv, token); + function disburseProtocol (address recv, address token) public { + disburseProtocolFees(recv, token); } function getPaidFees (address recv, address token) public view returns (uint128) { diff --git a/test/FacadePool.ts b/test/FacadePool.ts index ead6c1e..5face04 100644 --- a/test/FacadePool.ts +++ b/test/FacadePool.ts @@ -638,12 +638,6 @@ export class TestPool { await (await this.dex).connect(await this.auth).protocolCmd(this.COLD_PROXY, takeCmd, false) } - async setDefaultPathConversion (conversionPath: string[]) { - let abiCoder = new ethers.utils.AbiCoder() - let takeCmd = abiCoder.encode(["uint8", "address[]"], [122, conversionPath]); - await (await this.dex).connect(await this.auth).protocolCmd(this.LONG_PROXY, takeCmd, false) - } - async testRevisePoolIdx (idx: number, feeRate: number, protoTake: number, tickSize:number, jit: number = 0): Promise { let abiCoder = new ethers.utils.AbiCoder() From aa9b3d43e9be717f37c74d392c988e4e63a9ada0 Mon Sep 17 00:00:00 2001 From: cwsnt Date: Wed, 10 Jul 2024 07:10:25 +0700 Subject: [PATCH 5/5] 1/ address comment --- contracts/SdexEvents.sol | 2 +- contracts/callpaths/ColdPath.sol | 12 ++++++--- contracts/callpaths/LongPath.sol | 1 - contracts/callpaths/WarmPath.sol | 3 +-- contracts/interfaces/ISdexQuery.sol | 8 ------ contracts/libraries/README.md | 3 --- contracts/mixins/ProtocolAccount.sol | 39 +++++++++++++++++----------- contracts/test/TestProtocolAcct.sol | 4 +-- 8 files changed, 36 insertions(+), 36 deletions(-) delete mode 100644 contracts/interfaces/ISdexQuery.sol delete mode 100644 contracts/libraries/README.md diff --git a/contracts/SdexEvents.sol b/contracts/SdexEvents.sol index 184921b..66f2882 100644 --- a/contracts/SdexEvents.sol +++ b/contracts/SdexEvents.sol @@ -61,7 +61,7 @@ library SdexEvents { /* @notice Emitted when accumulated protocol fees are collected by the treasury. * @param token The token of the fees being collected. * @param recv The vault the collected fees are being paid to. */ - event ProtocolDividend (address indexed token, address indexed recv); + event ProtocolDividend (address[] indexed tokens, address indexed recv); /* @notice Called when any proxy sidecar contract is upgraded. * @param proxy The address of the new proxy smart contract. diff --git a/contracts/callpaths/ColdPath.sol b/contracts/callpaths/ColdPath.sol index d14a289..bf3defa 100644 --- a/contracts/callpaths/ColdPath.sol +++ b/contracts/callpaths/ColdPath.sol @@ -34,6 +34,9 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { using Chaining for Chaining.PairFlow; using ProtocolCmd for bytes; + /** @dev ONLY APPLY CONSTANT VARIABLE HERE */ + uint256 public constant TREASURY_START_TIME_OFFSET = 0 days; + /* @dev access control for treasury role */ modifier onlyTreasury() { require(msg.sender == treasury_, "Only Treasury"); @@ -249,20 +252,21 @@ contract ColdPath is MarketSequencer, DepositDesk, ProtocolAccount { * @param token The token for which the accumulated fees are being paid out. * (Or if 0x0 pays out native Ethereum.) */ function collectProtocol (bytes calldata cmd) private onlyTreasury { - (, address token) = abi.decode(cmd, (uint8, address)); + (, address[] memory tokens) = abi.decode(cmd, (uint8, address[])); require(block.timestamp >= treasuryStartTime_, "Treasury start"); - emit SdexEvents.ProtocolDividend(token, treasury_); - disburseProtocolFees(treasury_, token); + emit SdexEvents.ProtocolDividend(tokens, treasury_); + disburseProtocolFees(tokens); } /* @notice Sets the treasury address to receive protocol fees. Once set, the treasury cannot - * receive fees until 7 days after. */ + * receive fees until the start time offset. */ function setTreasury (bytes calldata cmd) private { (, address treasury) = abi.decode(cmd, (uint8, address)); require(treasury != address(0) && treasury.code.length != 0, "Treasury invalid"); treasury_ = treasury; + treasuryStartTime_ = uint64(block.timestamp + TREASURY_START_TIME_OFFSET); emit SdexEvents.TreasurySet(treasury_, treasuryStartTime_); } diff --git a/contracts/callpaths/LongPath.sol b/contracts/callpaths/LongPath.sol index c477efa..05b3876 100644 --- a/contracts/callpaths/LongPath.sol +++ b/contracts/callpaths/LongPath.sol @@ -6,7 +6,6 @@ import '../libraries/Directives.sol'; import '../libraries/Encoding.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/PriceGrid.sol'; -import '../libraries/ProtocolCmd.sol'; import '../mixins/MarketSequencer.sol'; import '../mixins/SettleLayer.sol'; import '../mixins/PoolRegistry.sol'; diff --git a/contracts/callpaths/WarmPath.sol b/contracts/callpaths/WarmPath.sol index ac589fd..dad11c7 100644 --- a/contracts/callpaths/WarmPath.sol +++ b/contracts/callpaths/WarmPath.sol @@ -11,7 +11,6 @@ import '../mixins/MarketSequencer.sol'; import '../mixins/SettleLayer.sol'; import '../mixins/PoolRegistry.sol'; import '../mixins/MarketSequencer.sol'; -import '../mixins/ProtocolAccount.sol'; import '../SdexEvents.sol'; /* @title Warm path callpath sidecar. @@ -31,7 +30,7 @@ import '../SdexEvents.sol'; * not state. As such it should never be called directly or externally, and should * only be invoked with DELEGATECALL so that it operates on the contract state * within the primary SdexSwap contract. */ -contract WarmPath is MarketSequencer, SettleLayer, ProtocolAccount { +contract WarmPath is MarketSequencer, SettleLayer { using SafeCast for uint128; using TokenFlow for TokenFlow.PairSeq; diff --git a/contracts/interfaces/ISdexQuery.sol b/contracts/interfaces/ISdexQuery.sol deleted file mode 100644 index 149a7ba..0000000 --- a/contracts/interfaces/ISdexQuery.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.19; - -import "../libraries/PoolSpecs.sol"; - -interface ISdexQuery { - function queryPoolParams (address base, address quote, uint256 poolIdx) external view returns (PoolSpecs.Pool memory pool); -} \ No newline at end of file diff --git a/contracts/libraries/README.md b/contracts/libraries/README.md deleted file mode 100644 index 17c6d13..0000000 --- a/contracts/libraries/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# SdexConvertLib -This SdexConvertLib is meant to be used for inside & outside integration to do the swap to this Sdex protocol (which support both direct & multihop swap) -This libraray is not 100% ready to be used, since it's still in progress and not fully tested & covered by unit tests. \ No newline at end of file diff --git a/contracts/mixins/ProtocolAccount.sol b/contracts/mixins/ProtocolAccount.sol index 105ba66..8ee58b8 100644 --- a/contracts/mixins/ProtocolAccount.sol +++ b/contracts/mixins/ProtocolAccount.sol @@ -18,6 +18,9 @@ import '../interfaces/IFeeProtocolCollector.sol'; contract ProtocolAccount is StorageLayout { using SafeCast for uint256; using TokenFlow for address; + + address public constant PROTOCOL_FEES_RECEIVER_HASH = + address(uint160(uint256(keccak256("PROTOCOL_FEES_RECEIVER_HASH")))); /* @notice Called at the completion of a swap event, incrementing any protocol * fees accumulated in the swap. */ @@ -37,21 +40,27 @@ contract ProtocolAccount is StorageLayout { } /* @notice Pays out the earned, but unclaimed protocol fees in the pool. - * @param recv - The receiver of the protocol fees. - * @param token - The token address of the quote token. - * @return withdrawn tokens */ - function disburseProtocolFees (address recv, address token) internal { - uint128 collected = feesAccum_[token]; - feesAccum_[token] = 0; - if (collected > 0) { - /** - * directly deposit token to fee protocol collector - */ - bytes32 payoutKey = keccak256(abi.encode(recv, token)); - userBals_[payoutKey].surplusCollateral_ += collected; - require(userBals_[payoutKey].surplusCollateral_ <= type(uint96).max, "Value exceeds uint96 range"); - IFeeProtocolCollector(treasury_).transferTokens(token, uint96(userBals_[payoutKey].surplusCollateral_)); - userBals_[payoutKey].surplusCollateral_ = 0; + * @param tokens - The token address of the quote token. + */ + function disburseProtocolFees (address[] memory tokens) internal { + for(uint256 i = 0; i < tokens.length; i++) { + address token = tokens[i]; + uint128 collected = feesAccum_[token]; + feesAccum_[token] = 0; + if (collected > 0) { + /** + * directly deposit token to fee protocol collector + */ + bytes32 payoutKey = keccak256(abi.encode(PROTOCOL_FEES_RECEIVER_HASH, token)); + userBals_[payoutKey].surplusCollateral_ += collected; + require(userBals_[payoutKey].surplusCollateral_ <= type(uint96).max, "Value exceeds uint96 range"); + + uint256 amountToTransfer = uint96(userBals_[payoutKey].surplusCollateral_); + + IERC20Minimal(token).approve(treasury_, amountToTransfer); + IFeeProtocolCollector(treasury_).transferTokens(token, uint96(amountToTransfer)); + userBals_[payoutKey].surplusCollateral_ = 0; + } } } } diff --git a/contracts/test/TestProtocolAcct.sol b/contracts/test/TestProtocolAcct.sol index cd8d7a5..94ee78b 100644 --- a/contracts/test/TestProtocolAcct.sol +++ b/contracts/test/TestProtocolAcct.sol @@ -32,8 +32,8 @@ contract TestProtocolAccount is ProtocolAccount { return feesAccum_[token]; } - function disburseProtocol (address recv, address token) public { - disburseProtocolFees(recv, token); + function disburseProtocol (address[] memory tokens) public { + disburseProtocolFees(tokens); } function getPaidFees (address recv, address token) public view returns (uint128) {