This commit is contained in:
dexorder
2024-10-17 02:42:28 -04:00
commit 25def69c66
878 changed files with 112489 additions and 0 deletions

72
src/more/Dexorder.sol Normal file
View File

@@ -0,0 +1,72 @@
pragma solidity 0.8.26;
import "@forge-std/console2.sol";
import "../core/OrderSpec.sol";
import {IVault} from "../interface/IVault.sol";
// represents the Dexorder organization
contract Dexorder {
address public immutable admin;
modifier onlyAdmin() {
require(msg.sender == admin, "not admin");
_;
}
constructor () {
admin = address(0);
}
receive() external payable {} // for testing purposes
// Execution batching
event DexorderExecutions(bytes16 indexed id, string[] errors);
struct ExecutionRequest {
address payable vault;
uint64 orderIndex;
uint8 trancheIndex;
PriceProof proof;
}
function execute( bytes16 id, ExecutionRequest memory req ) public returns (string memory error) {
// console2.log('Dexorder execute() single');
error = _execute(req);
string[] memory errors = new string[](1);
errors[0] = error;
emit DexorderExecutions(id, errors);
// console2.log('Dexorder execute() single completed');
}
function execute( bytes16 id, ExecutionRequest[] memory reqs ) public returns (string[] memory errors) {
// console2.log('Dexorder execute() multi');
// console2.log(reqs.length);
errors = new string[](reqs.length);
for( uint8 i=0; i<reqs.length; i++ )
errors[i] = _execute(reqs[i]);
emit DexorderExecutions(id, errors);
}
function _execute( ExecutionRequest memory req ) private returns (string memory error) {
// console2.log('Dexorder _execute()');
// single tranche execution
try IVault(req.vault).execute(req.orderIndex, req.trancheIndex, req.proof) {
error = '';
// console2.log('execution successful');
}
catch Error(string memory reason) {
if( bytes(reason).length == 0 )
reason = 'UNK';
// console2.log('execute error code');
// console2.log(reason);
error = reason;
}
}
}

153
src/more/FeeManagerLib.sol Normal file
View File

@@ -0,0 +1,153 @@
pragma solidity 0.8.26;
import {IFeeManager,FeeManager} from "../core/FeeManager.sol";
library FeeManagerLib {
function defaultFeeManager() internal returns (FeeManager) {
address payable a = payable(msg.sender);
return defaultFeeManager(a);
}
function defaultFeeManager(address payable owner) internal returns (FeeManager) {
return FeeManagerLib.defaultFeeManager(owner, owner, owner, owner);
}
function defaultFeeManager(
address admin,
address payable orderFeeAccount,
address payable gasFeeAccount,
address payable fillFeeAccount
) internal
returns (FeeManager) {
uint32 limitChangeNoticeDuration = 7 * 24 * 60 * 60; // 7 days
uint32 feeChangeNoticeDuration = 1 * 60 * 60; // 1 hour
uint8 maxIncreaseOrderFeePct = 10; // 10% per hour (within the limits)
uint8 maxIncreaseTrancheFeePct = 100; // 100% per hour (within the limits) gas prices can change quickly
uint8 orderFee = 0;
uint8 orderExp = 0;
// about 2.5¢ at $4000 ETH
uint8 gasFee = 181;
uint8 gasExp = 35;
uint8 fillFeeHalfBps = 30; // 15 bps fill fee
IFeeManager.FeeSchedule memory fees = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
IFeeManager.FeeSchedule memory limits = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
FeeManager.ConstructorArgs memory args = FeeManager.ConstructorArgs(
limitChangeNoticeDuration, feeChangeNoticeDuration, maxIncreaseOrderFeePct, maxIncreaseTrancheFeePct,
fees, limits, admin, orderFeeAccount, gasFeeAccount, fillFeeAccount
);
return new FeeManager(args);
}
function freeFeeManager() internal returns (FeeManager) {
address payable a = payable(msg.sender);
return freeFeeManager(a);
}
function freeFeeManager(address payable owner) internal returns (FeeManager) {
return FeeManagerLib.freeFeeManager(owner, owner, owner, owner);
}
function freeFeeManager(
address admin,
address payable orderFeeAccount,
address payable gasFeeAccount,
address payable fillFeeAccount
) internal
returns (FeeManager) {
uint32 limitChangeNoticeDuration = 5 * 60 * 60; // LIMIT_CHANGE_NOTICE_DURATION 5 minutes
uint32 feeChangeNoticeDuration = 2 * 60 * 60; // FEE_CHANGE_NOTICE_DURATION 2 minutes
uint8 maxIncreaseOrderFeePct = 10; // 10% per hour (within the limits)
uint8 maxIncreaseTrancheFeePct = 100; // 100% per hour (within the limits) gas prices can change quickly
uint8 orderFee = 0;
uint8 orderExp = 0;
uint8 gasFee = 0;
uint8 gasExp = 0;
uint8 fillFeeHalfBps = 0;
IFeeManager.FeeSchedule memory fees = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
IFeeManager.FeeSchedule memory limits = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
FeeManager.ConstructorArgs memory args = FeeManager.ConstructorArgs(
limitChangeNoticeDuration, feeChangeNoticeDuration, maxIncreaseOrderFeePct, maxIncreaseTrancheFeePct,
fees, limits, admin, orderFeeAccount, gasFeeAccount, fillFeeAccount
);
return new FeeManager(args);
}
function debugFeeManager() internal returns (FeeManager) {
return debugFeeManager(payable(msg.sender));
}
function debugFeeManager(address payable owner) internal returns (FeeManager) {
return FeeManagerLib.debugFeeManager(owner, owner, owner, owner);
}
function debugFeeManager(
address admin,
address payable orderFeeAccount,
address payable gasFeeAccount,
address payable fillFeeAccount
) internal
returns (FeeManager) {
uint32 limitChangeNoticeDuration = 5 * 60 * 60; // LIMIT_CHANGE_NOTICE_DURATION 5 minutes
uint32 feeChangeNoticeDuration = 2 * 60 * 60; // FEE_CHANGE_NOTICE_DURATION 2 minutes
uint8 maxIncreaseOrderFeePct = 10; // 10% per hour (within the limits)
uint8 maxIncreaseTrancheFeePct = 100; // 100% per hour (within the limits) gas prices can change quickly
// todo limits
// about $1 at $4000 ETH
uint8 orderFee = 227;
uint8 orderExp = 40;
// about 5¢ at $4000 ETH
uint8 gasFee = 181;
uint8 gasExp = 36;
uint8 fillFeeHalfBps = 30; // 15 bps fill fee
IFeeManager.FeeSchedule memory fees = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
IFeeManager.FeeSchedule memory limits = IFeeManager.FeeSchedule(
orderFee, orderExp,
gasFee, gasExp,
fillFeeHalfBps
);
FeeManager.ConstructorArgs memory args = FeeManager.ConstructorArgs(
limitChangeNoticeDuration, feeChangeNoticeDuration, maxIncreaseOrderFeePct, maxIncreaseTrancheFeePct,
fees, limits, admin, orderFeeAccount, gasFeeAccount, fillFeeAccount
);
return new FeeManager(args);
}
}

101
src/more/MockERC20.sol Normal file
View File

@@ -0,0 +1,101 @@
pragma solidity 0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@forge-std/console2.sol";
import {IERC20Metadata} from "../../lib_uniswap/v3-periphery/contracts/interfaces/IERC20Metadata.sol";
contract MockERC20 is IERC20Metadata {
// This token allows anyone to mint as much as they desire
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address=>uint256) private _balances;
mapping(address=>mapping(address=>uint256)) private _allowances;
constructor(string memory name_, string memory symbol_, uint8 decimals_)
{
// console2.log('MockERC20 constructor');
name = name_;
symbol = symbol_;
decimals = decimals_;
totalSupply = 0;
}
function mint(address account, uint256 amount) public {
// console2.log('MockERC20 mint');
_balances[account] += amount;
emit Transfer(address(this),account,amount);
}
function burn(uint256 amount) public {
// console2.log('MockERC20 burn');
require(_balances[msg.sender] >= amount);
_balances[msg.sender] -= amount;
emit Transfer(msg.sender,address(this),amount);
}
function balanceOf(address account) public view returns (uint256) {
// console2.log('MockERC20 balance');
return _balances[account];
}
function transfer(address to, uint256 value) public returns (bool) {
// console2.log('transfer');
// console2.log(msg.sender);
// console2.log(to);
// console2.log(value);
return _transferFrom(msg.sender, to, value);
}
function allowance(address owner, address spender) public view returns (uint256) {
// console2.log('MockERC20 allowance');
return _allowances[owner][spender];
}
function approve(address spender, uint256 value) public returns (bool) {
// console2.log('approve');
// console2.log(msg.sender);
// console2.log(spender);
// console2.log(value);
_allowances[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public returns (bool) {
// console2.log('transferFrom');
// console2.log(msg.sender);
// console2.log(from);
// console2.log(to);
// console2.log(value);
if( msg.sender != from ) {
// console2.log('allowance');
// console2.log(_allowances[from][msg.sender]);
require(value <= _allowances[from][msg.sender], 'Insufficient allowance');
if( _allowances[from][msg.sender] != type(uint256).max )
_allowances[from][msg.sender] -= value;
}
return _transferFrom(from, to, value);
}
function _transferFrom(address from, address to, uint256 value) private returns (bool) {
// console2.log('_transferFrom');
// console2.log(from);
// console2.log(to);
// console2.log(value);
// console2.log(_balances[from]);
require(_balances[from] >= value, 'Insufficient balance');
_balances[from] -= value;
_balances[to] += value;
emit Transfer(from,to,value);
// console2.log('raw transfer completed');
return true;
}
}

109
src/more/QueryHelper.sol Normal file
View File

@@ -0,0 +1,109 @@
pragma solidity 0.8.26;
import "@forge-std/console2.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../core/OrderSpec.sol";
import {IVault} from "../interface/IVault.sol";
import "@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "../core/UniswapV3.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
contract QueryHelper {
uint8 constant public version = 1;
uint8 constant public UNKNOWN_DECIMALS = type(uint8).max;
IUniswapV3Factory public immutable factory;
constructor( IUniswapV3Factory factory_ ) {
factory = factory_;
}
function getBalances( address vault, address[] memory tokens ) public view
returns (
uint256[] memory balances,
uint256[] memory decimals
) {
require(tokens.length < type(uint16).max);
balances = new uint256[](tokens.length);
decimals = new uint256[](tokens.length);
for( uint16 i=0; i < tokens.length; i++ ) {
try IERC20(tokens[i]).balanceOf(vault) returns (uint256 balance) {
balances[i] = balance;
}
catch {
balances[i] = 0;
}
try ERC20(tokens[i]).decimals() returns (uint8 dec) {
decimals[i] = dec;
}
catch {
decimals[i] = UNKNOWN_DECIMALS;
}
}
}
struct RoutesResult {
Exchange exchange;
uint24 fee;
address pool;
}
function getRoutes( address tokenA, address tokenB ) public view
returns(RoutesResult[] memory routes) {
// todo discover all supported pools
// console2.log('getRoutes');
// console2.log(tokenA);
// console2.log(tokenB);
// here we find the highest liquidity pool for v2 and for v3
uint24[4] memory fees = [uint24(100),500,3000,10000];
uint24 uniswapV2Fee = 0;
// uint128 uniswapV2Liquidity = 0;
// address uniswapV2Pool = address(0);
uint24 uniswapV3Fee = 0;
uint256 uniswapV3Liquidity = 0;
address uniswapV3Pool = address(0);
IERC20 ercA = IERC20(tokenA);
for( uint8 f=0; f<4; f++ ) {
// console2.log('getPool...');
uint24 fee = fees[f];
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(
address(UniswapV3Arbitrum.factory), PoolAddress.PoolKey(tokenA, tokenB, fee)));
if( address(pool) == address(0) ) {
// console2.log('no pool');
continue;
}
// console2.log('got pool');
// console2.log(address(pool));
// NOTE: pool.liquidity() is only the current tick's liquidity, so we look at the pool's balance
// of one of the tokens as a measure of liquidity
uint256 liquidity = ercA.balanceOf(address(pool));
// console2.log(liquidity);
if( liquidity > uniswapV3Liquidity ) {
uniswapV3Fee = fee;
uniswapV3Liquidity = liquidity;
uniswapV3Pool = address(pool);
}
}
uint8 routesCount = uniswapV3Fee > 0 ? 1 : 0 + uniswapV2Fee > 0 ? 1 : 0;
// console2.log(uniswapV3Pool);
// console2.log(uint(routesCount));
routes = new QueryHelper.RoutesResult[](routesCount);
uint8 i = 0;
// todo v2
if( uniswapV3Fee > 0 )
routes[i++] = QueryHelper.RoutesResult(Exchange.UniswapV3, uniswapV3Fee, uniswapV3Pool);
}
function poolStatus(IUniswapV3Pool pool) public view
returns (
int24 tick,
uint128 liquidity
) {
(, tick,,,,,) = pool.slot0();
liquidity = pool.liquidity();
}
}

34
src/more/VaultAddress.sol Normal file
View File

@@ -0,0 +1,34 @@
pragma solidity 0.8.26;
import "@forge-std/console2.sol";
library VaultAddress {
// keccak-256 hash of the Vault's bytecode (not the deployed bytecode but the initialization bytecode)
bytes32 public constant VAULT_INIT_CODE_HASH = 0xada4cd6b4cbd10f5eb48a27bde34a08fc1427562cff58ab798def9b1883d3f8a;
// the contract being constructed must not have any constructor arguments or the determinism will be broken.
// instead, use a callback to get construction arguments
// Uniswap example
// https://github.com/Uniswap/v3-periphery/blob/6cce88e63e176af1ddb6cc56e029110289622317/contracts/libraries/PoolAddress.sol#L33C5-L47C6
function computeAddress(address factory, address owner) internal pure returns (address vault) {
return computeAddress(factory, owner, 0);
}
function computeAddress(address factory, address owner, uint8 num) internal pure returns (address vault) {
bytes32 salt = keccak256(abi.encodePacked(owner,num));
vault = address(uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
salt,
VAULT_INIT_CODE_HASH
)
)
)
));
}
}