dexorder
This commit is contained in:
72
src/more/Dexorder.sol
Normal file
72
src/more/Dexorder.sol
Normal 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
153
src/more/FeeManagerLib.sol
Normal 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
101
src/more/MockERC20.sol
Normal 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
109
src/more/QueryHelper.sol
Normal 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
34
src/more/VaultAddress.sol
Normal 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
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user