vault deployment, query helper, bug fixes
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
cache/
|
||||
out/
|
||||
.idea/
|
||||
.env
|
||||
gen/
|
||||
/cache
|
||||
/out
|
||||
/.idea
|
||||
/.env
|
||||
/gen
|
||||
/lib
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
forge script script/Deploy.sol --fork-url http://localhost:8545 --broadcast
|
||||
./bin/build.sh
|
||||
forge script script/Deploy.sol -vvvv --fork-url http://localhost:8545 --broadcast
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
#db-migrate up
|
||||
#cd ../contract
|
||||
|
||||
anvil -f arbitrum_ankr &
|
||||
./bin/build.sh
|
||||
anvil -f arbitrum_ankr --chain-id 1338 &
|
||||
# todo check anvil result
|
||||
ANVIL_PID=$!
|
||||
sleep 2
|
||||
forge script script/Deploy.sol --fork-url http://localhost:8545 --broadcast
|
||||
|
||||
forge script script/Deploy.sol -vvvv --fork-url http://localhost:8545 --broadcast
|
||||
|
||||
trap_ctrlc() {
|
||||
echo
|
||||
@@ -1,4 +1,5 @@
|
||||
| Code | Name | Description |
|
||||
|------|--------------------|------------------------------------------------------------------------|
|
||||
| UC | Unknown Constraint | The constraint specification did not have a recognized Constraint Mode |
|
||||
| OCOM | Invalid OCO Mode | The OCO mode provided to placeOrder() is invalid. |
|
||||
| Code | Name | Description |
|
||||
|-------|--------------------|------------------------------------------------------------------------|
|
||||
| UC | Unknown Constraint | The constraint specification did not have a recognized Constraint Mode |
|
||||
| OCOM | Invalid OCO Mode | The OCO mode provided to placeOrder() is invalid. |
|
||||
| UR | Unknown Route | The specified order route is invalid. |
|
||||
1
node_modules
Symbolic link
1
node_modules
Symbolic link
@@ -0,0 +1 @@
|
||||
lib
|
||||
@@ -2,16 +2,24 @@
|
||||
pragma solidity =0.7.6;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import "../src/VaultDeployer.sol";
|
||||
import "forge-std/console2.sol";
|
||||
import "../src/QueryHelper.sol";
|
||||
import "../src/Factory.sol";
|
||||
import "../test/MockEnv.sol";
|
||||
|
||||
contract Deploy is Script {
|
||||
function run() external {
|
||||
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
|
||||
vm.startBroadcast(deployerPrivateKey);
|
||||
VaultDeployer deployer = new VaultDeployer{salt:keccak256(abi.encode(1))}();
|
||||
Factory deployer = new Factory{salt:keccak256(abi.encode(1))}();
|
||||
QueryHelper query = new QueryHelper();
|
||||
MockEnv mock = new MockEnv();
|
||||
vm.stopBroadcast();
|
||||
console2.log('VaultDeployer');
|
||||
console2.log(address(deployer));
|
||||
console2.log('QueryHelper');
|
||||
console2.log(address(query));
|
||||
console2.log('MockEnv'); // todo no mock in production deployment
|
||||
console2.log(address(mock));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ import "v3-core/contracts/UniswapV3Factory.sol";
|
||||
import "./VaultDeployer.sol";
|
||||
pragma abicoder v2;
|
||||
|
||||
|
||||
contract Factory is VaultDeployer {
|
||||
// todo owner
|
||||
address public admin;
|
||||
|
||||
|
||||
constructor() {
|
||||
admin = msg.sender;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,29 @@ library OrderLib {
|
||||
|
||||
event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut);
|
||||
|
||||
event DexorderCompleted (uint64 orderIndex);
|
||||
event DexorderCompleted (uint64 orderIndex); // todo remove?
|
||||
|
||||
event DexorderError (uint64 orderIndex, string reason);
|
||||
|
||||
// todo If a tranche fails to fill, an order can stay Open forever without any active tranches. maybe replace state with a simple canceled flag
|
||||
enum SwapOrderState {
|
||||
Open, Canceled, Filled, Template
|
||||
}
|
||||
|
||||
enum Exchange {
|
||||
UniswapV2,
|
||||
UniswapV3
|
||||
}
|
||||
|
||||
struct Route {
|
||||
Exchange exchange;
|
||||
uint24 fee;
|
||||
}
|
||||
|
||||
struct SwapOrder {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint24 fee;
|
||||
Route route;
|
||||
uint256 amount;
|
||||
bool amountIsInput;
|
||||
bool outputDirectlyToOwner;
|
||||
@@ -138,6 +149,8 @@ library OrderLib {
|
||||
revert('OCOM');
|
||||
for( uint8 o = 0; o < orders.length; o++ ) {
|
||||
SwapOrder memory order = orders[o];
|
||||
require(order.route.exchange == Exchange.UniswapV3, 'UR');
|
||||
// todo more order validation
|
||||
// we must explicitly copy into storage because Solidity doesn't implement copying the double-nested
|
||||
// tranches constraints array :(
|
||||
uint orderIndex = self.orders.length;
|
||||
@@ -147,7 +160,7 @@ library OrderLib {
|
||||
status.order.amountIsInput = order.amountIsInput;
|
||||
status.order.tokenIn = order.tokenIn;
|
||||
status.order.tokenOut = order.tokenOut;
|
||||
status.order.fee = order.fee;
|
||||
status.order.route = order.route;
|
||||
status.order.chainOrder = order.chainOrder;
|
||||
status.order.outputDirectlyToOwner = order.outputDirectlyToOwner;
|
||||
for( uint t=0; t<order.tranches.length; t++ ) {
|
||||
@@ -185,7 +198,8 @@ library OrderLib {
|
||||
Tranche storage tranche = status.order.tranches[tranche_index];
|
||||
uint160 sqrtPriceX96 = 0;
|
||||
uint160 sqrtPriceLimitX96 = 0;
|
||||
address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.fee);
|
||||
// todo other routes
|
||||
address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.route.fee);
|
||||
for (uint8 c = 0; c < tranche.constraints.length; c++) {
|
||||
Constraint storage constraint = tranche.constraints[c];
|
||||
if (constraint.mode == ConstraintMode.Time) {
|
||||
@@ -227,7 +241,11 @@ library OrderLib {
|
||||
amount = remaining;
|
||||
uint256 amountIn;
|
||||
uint256 amountOut;
|
||||
(error, amountIn, amountOut) = _do_execute_univ3(status.order, pool, amount, sqrtPriceLimitX96);
|
||||
if( status.order.route.exchange == Exchange.UniswapV3 )
|
||||
(error, amountIn, amountOut) = _do_execute_univ3(status.order, pool, amount, sqrtPriceLimitX96);
|
||||
// todo other routes
|
||||
else
|
||||
error = 'UR'; // unknown route
|
||||
if( bytes(error).length == 0 ) {
|
||||
status.filledIn += amountIn;
|
||||
status.filledOut += amountOut;
|
||||
@@ -248,12 +266,12 @@ library OrderLib {
|
||||
if (order.amountIsInput) {
|
||||
amountIn = amount;
|
||||
(error, amountOut) = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams(
|
||||
pool, order.tokenIn, order.tokenOut, order.fee, amount, sqrtPriceLimitX96));
|
||||
pool, order.tokenIn, order.tokenOut, order.route.fee, amount, sqrtPriceLimitX96));
|
||||
}
|
||||
else {
|
||||
amountOut = amount;
|
||||
(error, amountIn) = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams(
|
||||
pool, order.tokenIn, order.tokenOut, order.fee, amount, sqrtPriceLimitX96));
|
||||
pool, order.tokenIn, order.tokenOut, order.route.fee, amount, sqrtPriceLimitX96));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
86
src/QueryHelper.sol
Normal file
86
src/QueryHelper.sol
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "./OrderLib.sol";
|
||||
import "./Vault.sol";
|
||||
import "./VaultDeployer.sol";
|
||||
import "./Factory.sol";
|
||||
|
||||
contract QueryHelper {
|
||||
uint8 constant public version = 1;
|
||||
uint8 constant public UNKNOWN_DECIMALS = type(uint8).max;
|
||||
|
||||
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 {
|
||||
OrderLib.Exchange exchange;
|
||||
uint24 fee;
|
||||
address pool;
|
||||
}
|
||||
|
||||
function getRoutes( address tokenA, address tokenB ) public view
|
||||
returns(RoutesResult[] memory routes) {
|
||||
// todo discover all supported pools
|
||||
// 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;
|
||||
uint128 uniswapV3Liquidity = 0;
|
||||
address uniswapV3Pool = address(0);
|
||||
for( uint8 f=0; f<4; f++ ) {
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(Constants.uniswapV3Factory.getPool(tokenA, tokenB, fees[f]));
|
||||
try pool.liquidity() returns (uint128 liquidity) {
|
||||
// todo v2
|
||||
if( liquidity > uniswapV3Liquidity ) {
|
||||
uniswapV3Fee = fees[f];
|
||||
uniswapV3Liquidity = liquidity;
|
||||
uniswapV3Pool = address(pool);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}
|
||||
uint8 routesCount = uniswapV3Fee > 0 ? 1 : 0 + uniswapV2Fee > 0 ? 1 : 0;
|
||||
routes = new QueryHelper.RoutesResult[](routesCount);
|
||||
uint8 i = 0;
|
||||
// todo v2
|
||||
if( uniswapV3Fee > 0 )
|
||||
routes[i++] = QueryHelper.RoutesResult(OrderLib.Exchange.UniswapV3, uniswapV3Fee, uniswapV3Pool);
|
||||
}
|
||||
|
||||
function poolStatus(IUniswapV3Pool pool) public view
|
||||
returns (
|
||||
int24 tick,
|
||||
uint128 liquidity
|
||||
) {
|
||||
(, tick,,,,,) = pool.slot0();
|
||||
liquidity = pool.liquidity();
|
||||
}
|
||||
}
|
||||
@@ -9,20 +9,24 @@ library VaultAddress {
|
||||
// keccak-256 hash of the Vault's bytecode (not the deployed bytecode but the initialization bytecode)
|
||||
// can paste into:
|
||||
// https://emn178.github.io/online-tools/keccak_256.html
|
||||
bytes32 internal constant VAULT_INIT_CODE_HASH = 0xbf043f7035d5aa3be2b3c94df5b256fbe24675689327af4ab71c48194c463031;
|
||||
bytes32 internal constant VAULT_INIT_CODE_HASH = 0xdc7f6d1305b2c9951dc98f6a745290e4f2b92bf65d346db11dd284dcfcd67aef;
|
||||
|
||||
// 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) {
|
||||
vault = address(
|
||||
uint256(
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
hex'ff',
|
||||
factory,
|
||||
keccak256(abi.encode(owner)),
|
||||
keccak256(abi.encode(owner,num)),
|
||||
VAULT_INIT_CODE_HASH
|
||||
)
|
||||
)
|
||||
|
||||
@@ -10,14 +10,31 @@ contract VaultDeployer {
|
||||
address owner;
|
||||
}
|
||||
|
||||
event VaultCreated( address deployer, address owner );
|
||||
event VaultCreated( address indexed owner, uint8 num );
|
||||
|
||||
Parameters public parameters;
|
||||
|
||||
function deployVault(address owner) public returns (address payable vault) {
|
||||
parameters = Parameters(owner);
|
||||
vault = address(new Vault{salt: keccak256(abi.encode(owner))}());
|
||||
delete parameters;
|
||||
emit VaultCreated( address(this), owner );
|
||||
function deployVault() public returns (address payable vault) {
|
||||
return _deployVault(msg.sender, 0);
|
||||
}
|
||||
|
||||
function deployVault(uint8 num) public returns (address payable vault) {
|
||||
return _deployVault(msg.sender, num);
|
||||
}
|
||||
|
||||
function deployVault(address owner) public returns (address payable vault) {
|
||||
return _deployVault(owner, 0);
|
||||
}
|
||||
|
||||
function deployVault(address owner, uint8 num) public returns (address payable vault) {
|
||||
return _deployVault(owner, num);
|
||||
}
|
||||
|
||||
function _deployVault(address owner, uint8 num) internal returns (address payable vault) {
|
||||
parameters = Parameters(owner);
|
||||
vault = address(new Vault{salt: keccak256(abi.encode(owner,num))}());
|
||||
delete parameters;
|
||||
emit VaultCreated( owner, num );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,19 +21,22 @@ contract MockEnv {
|
||||
uint24 public fee;
|
||||
MockERC20 public COIN;
|
||||
MockERC20 public USD;
|
||||
address public token0; // either WETH or USDC depending on the order in the pool
|
||||
address public token0; // either COIN or USD depending on the order in the pool
|
||||
address public token1;
|
||||
bool public inverted;
|
||||
|
||||
|
||||
function init() public {
|
||||
// sets up two mock coins COIN and USD, plus a uniswap v3 pool.
|
||||
// the initial price is 1.000000, but since COIN has 18 decimals and USD only has 6, the raw pool price is 1e-12
|
||||
// therefore the sqrt price is 1e-6
|
||||
// 1000e12 liquidity is put into the pool at each tick spacing for 10 tick spacings to either side of $1
|
||||
function init() internal {
|
||||
COIN = new MockERC20('Mock Coin', 'MOCK', 18);
|
||||
USD = new MockERC20('Universally Supported Dollars', 'USD', 6);
|
||||
fee = 500;
|
||||
inverted = address(COIN) > address(USD);
|
||||
token0 = inverted ? address(USD) : address(COIN);
|
||||
token1 = inverted ? address(COIN) : address(USD);
|
||||
uint160 initialPrice = 1 * 2**96;
|
||||
uint160 initialPrice = uint160(79228162514264337593543); // price 1e-12 = sqrt price 1e-6 = 2**96 / 10**6
|
||||
// if this reverts here make sure Anvil is started and you are running forge with --rpc-url
|
||||
pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, initialPrice));
|
||||
int24 ts = pool.tickSpacing();
|
||||
@@ -42,7 +45,7 @@ contract MockEnv {
|
||||
for (int8 i = 0; i < 10; i++) {
|
||||
lower -= ts;
|
||||
upper += ts;
|
||||
stake(1 * 10 ** COIN.decimals(), 1000 * 10 ** USD.decimals(), lower, upper);
|
||||
stake(1000 * 10**12, lower, upper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
test/TestOrder.sol
Normal file
29
test/TestOrder.sol
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "./MockEnv.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "../src/Factory.sol";
|
||||
|
||||
contract TestOrder is MockEnv, Test {
|
||||
Factory public factory;
|
||||
Vault public vault;
|
||||
|
||||
// vault gets 100,000 COIN and 100,000 USD
|
||||
function setUp() public {
|
||||
factory = new Factory();
|
||||
vault = Vault(factory.deployVault(address(this)));
|
||||
uint256 coinAmount = 100_000 * 10 ** COIN.decimals();
|
||||
COIN.mint(address(vault), coinAmount);
|
||||
uint256 usdAmount = 100_000 * 10 ** USD.decimals();
|
||||
USD.mint(address(vault), usdAmount);
|
||||
}
|
||||
|
||||
|
||||
function testOrder() public {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,9 +4,10 @@ pragma solidity =0.7.6;
|
||||
import "forge-std/console2.sol";
|
||||
import "../src/Factory.sol";
|
||||
import "../src/VaultAddress.sol";
|
||||
import "forge-std/Test.sol";
|
||||
pragma abicoder v2;
|
||||
|
||||
contract TestVault {
|
||||
contract TestVault is Test{
|
||||
|
||||
Factory public factory;
|
||||
Vault public vault;
|
||||
|
||||
Reference in New Issue
Block a user