diff --git a/packages/portal-contracts/src/Portal.sol b/packages/portal-contracts/src/Portal.sol index 41f4770..e61aba9 100644 --- a/packages/portal-contracts/src/Portal.sol +++ b/packages/portal-contracts/src/Portal.sol @@ -78,6 +78,13 @@ contract Portal is Owned { address maker ); + event OrderUpdated( + uint256 orderID, + int128 amountSats, + uint256 makerStakedTok, + address maker + ); + event OrderCancelled(uint256 orderID); event OrderMatched( @@ -414,6 +421,42 @@ contract Portal is Owned { _transferToSender(tokToSend); } + // Update an existing bid or ask order. amountSats is negative if the order is an ask. + function updateOrder(uint256 orderID, int128 amountSats) public payable { + Order storage o = orderbook[orderID]; + + require(o.amountSats != 0, "Order not found"); + require(o.maker == msg.sender, "Order not yours"); + + if (o.amountSats < 0) { + // This is an ask. Receive the ether to be sold and update the order. + require(amountSats < 0 && uint256(int256(-(o.amountSats + amountSats))) <= MAX_SATS, "Must be adding a valid amount of liquidity"); + + uint256 additionalValue = uint256(int256(-amountSats * int128(o.priceTokPerSat))); + o.amountSats += amountSats; + + _transferFromSender(additionalValue); + } else { + // This is a bid, update the order and take the additional stake + require(amountSats > 0 && uint256(int256(o.amountSats)) + uint256(int256(amountSats)) <= MAX_SATS, "Must be adding a valid amount of liquidity"); + uint256 expectedStakeTok = (uint256(int256(amountSats)) * uint256(o.priceTokPerSat) * stakePercent) / 100; + + // Update the order + o.amountSats += amountSats; + o.stakedTok += expectedStakeTok; + + // Receive the additional stake + _transferFromSender(expectedStakeTok); + } + + emit OrderUpdated( + orderID, + o.amountSats, + o.stakedTok, + o.maker + ); + } + function _transferFromSender(uint256 tok) private { if (address(token) == address(0)) { // Receive wei diff --git a/packages/portal-contracts/src/test/Portal.t.sol b/packages/portal-contracts/src/test/Portal.t.sol index a78d8d3..6a009bf 100644 --- a/packages/portal-contracts/src/test/Portal.t.sol +++ b/packages/portal-contracts/src/test/Portal.t.sol @@ -13,6 +13,13 @@ contract PortalTest is Test { uint256 makerStakedWei, address maker ); + + event OrderUpdated( + uint256 orderID, + int128 amountSats, + uint256 makerStakedTok, + address maker + ); event OrderCancelled(uint256 orderID); @@ -360,6 +367,52 @@ contract PortalTest is Test { emit EscrowSettled(2, 1e8-1e7, address(this), 18.9 ether); p.proveSettlement(2, 123, proof, 12); } + + function testUpdateOrderNotMaker() public { + Portal p = testBid(); + vm.prank(address(0x42)); + vm.expectRevert(bytes("Order not yours")); + p.updateOrder(1, 1e8); + } + + function testUpdateBid() public { + Portal p = testBid(); + + uint256 stakeWei = 1 ether; /* 1 ETH = 5% of 1 * 20 ETH */ + vm.expectEmit(true, true, true, true); + emit OrderUpdated(1, 2e8, 2e18, address(this)); + p.updateOrder{value: stakeWei}(1, 1e8); + + uint256 orderID = 1; + bytes20 destScriptHash = hex"0011223344556677889900112233445566778899"; + uint256 escrowID = p.initiateSell{value: 40 ether}( + orderID, + 2e8, + destScriptHash + ); + assertEq(escrowID, 1); + + vm.expectRevert(bytes("Order already filled")); + p.initiateSell{value: 20 ether}(orderID, 1e8, destScriptHash); + } + + function testUpdateAsk() public { + Portal p = testAsk(); + + vm.expectEmit(true, true, true, true); + emit OrderUpdated(1, -2e8, 0, address(this)); + p.updateOrder{value: 20 ether}(1, -1e8); + + uint256 orderID = 1; + uint256 escrowID = p.initiateBuy{value: 2 ether}( + orderID, + 2e8 + ); + assertEq(escrowID, 1); + + vm.expectRevert(bytes("Order already filled")); + p.initiateBuy{value: 20 ether}(orderID, 1e8); + } } contract StubBtcTxVerifier is IBtcTxVerifier {