reworked to optionally use Hardhat in mock; chain id 31337; refactored TransactionJob management; execute() mostly commented out for minimalism

This commit is contained in:
Tim Olson
2023-10-26 16:56:08 -04:00
parent 904549f564
commit f775f86960
10 changed files with 119 additions and 67 deletions

View File

@@ -9,7 +9,7 @@ forge build "$@" || exit 1
VAULT_INIT_CODE_HASH=$(cast keccak $(jq -r .bytecode.object < out/Vault.sol/Vault.json)) VAULT_INIT_CODE_HASH=$(cast keccak $(jq -r .bytecode.object < out/Vault.sol/Vault.json))
# put the hash value into the VaultAddress.sol source file # put the hash value into the VaultAddress.sol source file
sed -i "s/bytes32 internal constant VAULT_INIT_CODE_HASH = .*;/bytes32 internal constant VAULT_INIT_CODE_HASH = $VAULT_INIT_CODE_HASH;/" src/VaultAddress.sol sed -i "s/VAULT_INIT_CODE_HASH = .*;/VAULT_INIT_CODE_HASH = $VAULT_INIT_CODE_HASH;/" src/VaultAddress.sol
# generate a javascript file with the constant # generate a javascript file with the constant
mkdir gen &> /dev/null mkdir gen &> /dev/null

View File

@@ -6,7 +6,7 @@
#cd ../contract #cd ../contract
#./bin/build.sh #./bin/build.sh
anvil -f arbitrum_mock --chain-id 1338 & anvil -f arbitrum_mock --chain-id 31337 &
# todo check anvil result # todo check anvil result
ANVIL_PID=$! ANVIL_PID=$!
sleep 2 sleep 2

View File

@@ -12,7 +12,8 @@ contract Deploy is Script {
function run() external { function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey); vm.startBroadcast(deployerPrivateKey);
Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); // Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); // version 1
Factory deployer = new Factory(); // hardhat often breaks on the CREATE2 above :(
QueryHelper query = new QueryHelper(); QueryHelper query = new QueryHelper();
Dexorder dexorder = new Dexorder(); Dexorder dexorder = new Dexorder();
MockEnv mock = new MockEnv(); MockEnv mock = new MockEnv();

View File

@@ -8,7 +8,6 @@ pragma abicoder v2;
contract Factory is VaultDeployer { contract Factory is VaultDeployer {
address public admin; address public admin;
constructor() { constructor() {
admin = msg.sender; admin = msg.sender;
} }

View File

@@ -191,69 +191,80 @@ library OrderLib {
// TE current time is too early for this tranche // TE current time is too early for this tranche
// TL current time is too late for this tranche // TL current time is too late for this tranche
// //
function execute(OrdersInfo storage self, address owner, uint64 orderIndex, uint8 tranche_index, PriceProof memory proof) internal { function execute(OrdersInfo storage self, address owner, uint64 orderIndex, uint8 trancheIndex, PriceProof memory proof) internal {
console2.log('execute');
console2.log(address(this));
console2.log(uint(orderIndex));
console2.log(uint(trancheIndex));
SwapOrderStatus storage status = self.orders[orderIndex]; SwapOrderStatus storage status = self.orders[orderIndex];
if (status.state != SwapOrderState.Open) if (status.state != SwapOrderState.Open)
revert('NO'); // Not Open revert('NO'); // Not Open
Tranche storage tranche = status.order.tranches[tranche_index]; Tranche storage tranche = status.order.tranches[trancheIndex];
uint160 sqrtPriceX96 = 0; uint160 sqrtPriceX96 = 0;
uint160 sqrtPriceLimitX96 = 0; uint160 sqrtPriceLimitX96 = 0; // 0 means "not set yet" and 1 is the minimum value
// todo other routes // todo other routes
address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.route.fee); address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.route.fee);
for (uint8 c = 0; c < tranche.constraints.length; c++) { // for (uint8 c = 0; c < tranche.constraints.length; c++) {
Constraint storage constraint = tranche.constraints[c]; // Constraint storage constraint = tranche.constraints[c];
if (constraint.mode == ConstraintMode.Time) { // if (constraint.mode == ConstraintMode.Time) {
TimeConstraint memory tc = abi.decode(constraint.constraint, (TimeConstraint)); // TimeConstraint memory tc = abi.decode(constraint.constraint, (TimeConstraint));
uint32 time = tc.earliest.mode == TimeMode.Timestamp ? tc.earliest.time : status.start + tc.earliest.time; // uint32 time = tc.earliest.mode == TimeMode.Timestamp ? tc.earliest.time : status.start + tc.earliest.time;
if (time > block.timestamp) // if (time > block.timestamp)
revert('TE'); // time early // revert('TE'); // time early
time = tc.latest.mode == TimeMode.Timestamp ? tc.latest.time : status.start + tc.latest.time; // time = tc.latest.mode == TimeMode.Timestamp ? tc.latest.time : status.start + tc.latest.time;
if (time < block.timestamp) // if (time < block.timestamp)
revert('TL'); // time late // revert('TL'); // time late
} // }
else if (constraint.mode == ConstraintMode.Limit) { // else if (constraint.mode == ConstraintMode.Limit) {
if( sqrtPriceX96 == 0 ) { // if( sqrtPriceX96 == 0 ) {
(sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); // (sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
} // }
PriceConstraint memory pc = abi.decode(constraint.constraint, (PriceConstraint)); // PriceConstraint memory pc = abi.decode(constraint.constraint, (PriceConstraint));
uint256 price = sqrtPriceX96; // uint256 price = sqrtPriceX96;
if( pc.isRatio ) // if( pc.isRatio )
pc.valueSqrtX96 = uint160(price * pc.valueSqrtX96 / 2**96); // todo overflow check! // pc.valueSqrtX96 = uint160(price * pc.valueSqrtX96 / 2**96); // todo overflow check!
if( pc.isAbove && price < pc.valueSqrtX96 || !pc.isAbove && price > pc.valueSqrtX96 ) // if( pc.isAbove && price < pc.valueSqrtX96 || !pc.isAbove && price > pc.valueSqrtX96 )
revert('L'); // revert('L');
} // if( sqrtPriceLimitX96 == 0 ||
else if (constraint.mode == ConstraintMode.Barrier) { // pc.isAbove && pc.valueSqrtX96 < sqrtPriceLimitX96 ||
revert('NI'); // not implemented // !pc.isAbove && pc.valueSqrtX96 > sqrtPriceLimitX96
} // )
else if (constraint.mode == ConstraintMode.Trailing) { // sqrtPriceLimitX96 = pc.valueSqrtX96;
revert('NI'); // not implemented // }
} // else if (constraint.mode == ConstraintMode.Barrier) {
else if (constraint.mode == ConstraintMode.Line) { // revert('NI'); // not implemented
revert('NI'); // not implemented // }
} // else if (constraint.mode == ConstraintMode.Trailing) {
else // unknown constraint // revert('NI'); // not implemented
revert('NI'); // not implemented // }
} // else if (constraint.mode == ConstraintMode.Line) {
// revert('NI'); // not implemented
// }
// else // unknown constraint
// revert('NI'); // not implemented
// }
uint256 amount = status.order.amount * tranche.fraction / type(uint16).max // the most this tranche could do uint256 amount = status.order.amount * tranche.fraction / type(uint16).max // the most this tranche could do
- (status.order.amountIsInput ? status.trancheFilledIn[tranche_index] : status.trancheFilledOut[tranche_index]); // minus tranche fills - (status.order.amountIsInput ? status.trancheFilledIn[trancheIndex] : status.trancheFilledOut[trancheIndex]); // minus tranche fills
// order amount remaining // order amount remaining
uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut); uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut);
if (amount > remaining) // not more than the order's overall remaining amount if (amount > remaining) // not more than the order's overall remaining amount
amount = remaining; amount = remaining;
console2.log(amount);
address recipient = status.order.outputDirectlyToOwner ? owner : address(this); address recipient = status.order.outputDirectlyToOwner ? owner : address(this);
console2.log(recipient);
uint256 amountIn; uint256 amountIn;
uint256 amountOut; uint256 amountOut;
if( status.order.route.exchange == Exchange.UniswapV3 ) // if( status.order.route.exchange == Exchange.UniswapV3 )
(amountIn, amountOut) = _do_execute_univ3(recipient, status.order, pool, amount, sqrtPriceLimitX96); (amountIn, amountOut) = _do_execute_univ3(recipient, status.order, pool, amount, sqrtPriceLimitX96);
// todo other routes // todo other routes
else // else
revert('UR'); // unknown route // revert('UR'); // unknown route
status.filledIn += amountIn; // status.filledIn += amountIn;
status.filledOut += amountOut; // status.filledOut += amountOut;
status.trancheFilledIn[tranche_index] += amountIn; // status.trancheFilledIn[trancheIndex] += amountIn;
status.trancheFilledOut[tranche_index] += amountOut; // status.trancheFilledOut[trancheIndex] += amountOut;
emit DexorderSwapFilled(orderIndex, tranche_index, amountIn, amountOut); // emit DexorderSwapFilled(orderIndex, trancheIndex, amountIn, amountOut);
_checkCompleted(self, orderIndex, status); // _checkCompleted(self, orderIndex, status);
} }
@@ -261,9 +272,8 @@ library OrderLib {
returns (uint256 amountIn, uint256 amountOut) returns (uint256 amountIn, uint256 amountOut)
{ {
// todo refactor this signature to be more low-level, taking only the in/out amounts and limit prices. doesnt need self/status/index // todo refactor this signature to be more low-level, taking only the in/out amounts and limit prices. doesnt need self/status/index
if (sqrtPriceLimitX96 == 0) console2.log('price limit');
// check pool inversion to see if the price should be high or low console2.log(uint(sqrtPriceLimitX96));
sqrtPriceLimitX96 = order.tokenIn < order.tokenOut ? 0 : type(uint160).max;
if (order.amountIsInput) { if (order.amountIsInput) {
amountIn = amount; amountIn = amount;
amountOut = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams( amountOut = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams(

View File

@@ -5,6 +5,7 @@ pragma abicoder v2;
import "./Constants.sol"; import "./Constants.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "v3-periphery/libraries/TransferHelper.sol"; import "v3-periphery/libraries/TransferHelper.sol";
import "v3-core/contracts/libraries/TickMath.sol";
import "forge-std/console2.sol"; import "forge-std/console2.sol";
@@ -33,12 +34,20 @@ library UniswapSwapper {
// uint160 sqrtPriceLimitX96; // uint160 sqrtPriceLimitX96;
// } // }
console2.log('swapExactInput approve...'); console2.log('swapExactInput approve...');
TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), params.amount); console2.log(address(this));
console2.log(params.tokenIn); console2.log(params.tokenIn);
console2.log(params.tokenOut); console2.log(params.tokenOut);
console2.log(uint(params.fee)); console2.log(uint(params.fee));
console2.log(address(Constants.uniswapV3SwapRouter)); console2.log(address(params.recipient));
console2.log(params.amount); console2.log(params.amount);
console2.log(uint(params.sqrtPriceLimitX96));
console2.log(address(Constants.uniswapV3SwapRouter));
if (params.sqrtPriceLimitX96 == 0)
params.sqrtPriceLimitX96 = params.tokenIn < params.tokenOut ? TickMath.MIN_SQRT_RATIO+1 : TickMath.MAX_SQRT_RATIO-1;
TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), params.amount);
console2.log('splx96');
console2.log(uint(params.sqrtPriceLimitX96));
amountOut = Constants.uniswapV3SwapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ amountOut = Constants.uniswapV3SwapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({
tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: params.recipient, tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: params.recipient,
deadline: block.timestamp, amountIn: params.amount, amountOutMinimum: 0, sqrtPriceLimitX96: params.sqrtPriceLimitX96 deadline: block.timestamp, amountIn: params.amount, amountOutMinimum: 0, sqrtPriceLimitX96: params.sqrtPriceLimitX96
@@ -50,6 +59,8 @@ library UniswapSwapper {
function swapExactOutput(SwapParams memory params) internal returns (uint256 amountIn) function swapExactOutput(SwapParams memory params) internal returns (uint256 amountIn)
{ {
// TODO copy changes over from swapExactInput
// struct ExactOutputSingleParams { // struct ExactOutputSingleParams {
// address tokenIn; // address tokenIn;
// address tokenOut; // address tokenOut;

View File

@@ -16,7 +16,7 @@ contract Vault {
uint8 public immutable version; uint8 public immutable version;
address public immutable owner; address public immutable owner;
OrderLib.OrdersInfo public orderList; OrderLib.OrdersInfo public ordersInfo;
constructor() constructor()
{ {
@@ -54,23 +54,26 @@ contract Vault {
token.transfer(recipient, amount); token.transfer(recipient, amount);
} }
function numSwapOrders() external view returns (uint64 num) {
return uint64(ordersInfo.orders.length);
}
function placeOrder(OrderLib.SwapOrder memory order) public onlyOwner { function placeOrder(OrderLib.SwapOrder memory order) public onlyOwner {
console2.log('Vault.placeOrder()'); console2.log('Vault.placeOrder()');
orderList._placeOrder(order); ordersInfo._placeOrder(order);
} }
function placeOrders(OrderLib.SwapOrder[] memory orders, OrderLib.OcoMode ocoMode) public onlyOwner { function placeOrders(OrderLib.SwapOrder[] memory orders, OrderLib.OcoMode ocoMode) public onlyOwner {
orderList._placeOrders(orders, ocoMode); ordersInfo._placeOrders(orders, ocoMode);
} }
function swapOrderStatus(uint64 orderIndex) public view returns (OrderLib.SwapOrderStatus memory status) { function swapOrderStatus(uint64 orderIndex) external view returns (OrderLib.SwapOrderStatus memory status) {
return orderList.orders[orderIndex]; return ordersInfo.orders[orderIndex];
} }
function execute(uint64 orderIndex, uint8 tranche_index, OrderLib.PriceProof memory proof) public function execute(uint64 orderIndex, uint8 tranche_index, OrderLib.PriceProof memory proof) public
{ {
orderList.execute(owner, orderIndex, tranche_index, proof); ordersInfo.execute(owner, orderIndex, tranche_index, proof);
} }
modifier onlyOwner() { modifier onlyOwner() {

View File

@@ -10,7 +10,7 @@ library VaultAddress {
// keccak-256 hash of the Vault's bytecode (not the deployed bytecode but the initialization bytecode) // keccak-256 hash of the Vault's bytecode (not the deployed bytecode but the initialization bytecode)
// can paste into: // can paste into:
// https://emn178.github.io/online-tools/keccak_256.html // https://emn178.github.io/online-tools/keccak_256.html
bytes32 internal constant VAULT_INIT_CODE_HASH = 0x72f8226a3666abba278cd472062b776a74be697e53a365b69f0e9bfbcfabc1b9; bytes32 public constant VAULT_INIT_CODE_HASH = 0xaa3457854b70ea8d66f3b73269f0cf34c7e2212e4b4bd8176e8388ff223a2bd0;
// the contract being constructed must not have any constructor arguments or the determinism will be broken. instead, use a callback to // the contract being constructed must not have any constructor arguments or the determinism will be broken. instead, use a callback to
// get construction arguments // get construction arguments

View File

@@ -29,9 +29,13 @@ contract MockEnv {
// the initial price is 1.000000, but since COIN has 18 decimals and USD only has 6, the raw pool price is 1e-12 // 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 // 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 // 1000e12 liquidity is put into the pool at each tick spacing for 10 tick spacings to either side of $1
function init() internal { function init() public {
COIN = new MockERC20('Mock Coin', 'MOCK', 18); COIN = new MockERC20('Mock Coin', 'MOCK', 18);
USD = new MockERC20('Universally Supported Dollars', 'USD', 6); console2.log('COIN');
console2.log(address(COIN));
USD = new MockERC20('Universally Stable Denomination', 'USD', 6);
console2.log('USD');
console2.log(address(USD));
fee = 500; fee = 500;
inverted = address(COIN) > address(USD); inverted = address(COIN) > address(USD);
token0 = inverted ? address(USD) : address(COIN); token0 = inverted ? address(USD) : address(COIN);
@@ -40,7 +44,8 @@ contract MockEnv {
console2.log('if this is the last line before a revert then make sure to run forge with --rpc-url'); console2.log('if this is the last line before a revert then make sure to run forge with --rpc-url');
// if this reverts here make sure Anvil is started and you are running forge with --rpc-url // 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)); pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, initialPrice));
console2.log('created v3 pool successfully'); console2.log('v3 pool');
console2.log(address(pool));
int24 ts = pool.tickSpacing(); int24 ts = pool.tickSpacing();
(, int24 lower, , , , ,) = pool.slot0(); (, int24 lower, , , , ,) = pool.slot0();
int24 upper = lower; int24 upper = lower;

View File

@@ -9,6 +9,8 @@ import "../src/Factory.sol";
import "../src/OrderLib.sol"; import "../src/OrderLib.sol";
contract TestOrder is MockEnv, Test { contract TestOrder is MockEnv, Test {
using OrderLib for OrderLib.OrdersInfo;
Factory public factory; Factory public factory;
Vault public vault; Vault public vault;
@@ -24,7 +26,7 @@ contract TestOrder is MockEnv, Test {
} }
function testOrder() public { function testPlaceOrder() public {
OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](3); OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](3);
OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1); OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1);
constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Time, bytes(hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000046500")); constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Time, bytes(hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000046500"));
@@ -44,4 +46,25 @@ contract TestOrder is MockEnv, Test {
vault.placeOrder(order); vault.placeOrder(order);
} }
function testExecuteOrder() public {
OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](1);
OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1);
constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Time, bytes(hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000046500"));
tranches[0] = OrderLib.Tranche(type(uint16).max,constraints1);
uint256 amount = 3*10**COIN.decimals() / 10; // 0.3 COIN
COIN.mint(address(vault), amount); // create COIN to sell
OrderLib.SwapOrder memory order = OrderLib.SwapOrder(
address(COIN), address(USD), // sell COIN for USD
OrderLib.Route(OrderLib.Exchange.UniswapV3, 500), amount, true, false,
OrderLib.NO_CHAIN, tranches
);
uint64 orderIndex = vault.numSwapOrders();
vault.placeOrder(order);
console2.log('placed order');
console2.log(uint(orderIndex));
string memory result;
vault.execute(orderIndex, 0, OrderLib.PriceProof(0));
console2.log('executed');
}
} }