diff --git a/onchain/src/Stake.sol b/onchain/src/Stake.sol index 522d2d0..03dbec7 100644 --- a/onchain/src/Stake.sol +++ b/onchain/src/Stake.sol @@ -30,6 +30,7 @@ contract Stake is IStake { error PositionNotFound(); event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 share, uint32 creationTime, uint32 taxRate); + event TaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxAmount); event PositionRemoved(uint256 indexed positionId, uint256 share, uint32 lastTaxTime); struct StakingPosition { @@ -123,7 +124,7 @@ contract Stake is IStake { } // dissolve position // TODO: what if someone calls payTax and exitPosition in the same transaction? - _payTax(pos, 0); + _payTax(positionsToSnatch[i], pos, 0); _exitPosition(positionsToSnatch[i], pos); // TODO: exit positions partially, if needed // TODO: avoid greeving where more positions are freed than needed. @@ -151,20 +152,31 @@ contract Stake is IStake { emit PositionCreated(positionId, sp.owner, sp.share, sp.creationTime, sp.taxRate); } + function changeTax(uint256 positionID, uint32 taxRate) public { + StakingPosition storage pos = positions[positionID]; + if (pos.owner != msg.sender) { + revert NoPermission(msg.sender, pos.owner); + } + // to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION + require(taxRate > pos.taxRate); + _payTax(positionID, pos, TAX_FLOOR_DURATION); + pos.taxRate = taxRate; + } + function exitPosition(uint256 positionId) public { StakingPosition storage pos = positions[positionId]; if (pos.owner != msg.sender) { revert NoPermission(msg.sender, pos.owner); } // to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION - _payTax(pos, TAX_FLOOR_DURATION); + _payTax(positionId, pos, TAX_FLOOR_DURATION); _exitPosition(positionId, pos); } function payTax(uint256 positionID) public { StakingPosition storage pos = positions[positionID]; // TODO: what if someone calls payTax and exitPosition in the same transaction? - _payTax(pos, 0); + _payTax(positionID, pos, 0); } function taxDue(uint256 positionID, uint256 taxFloorDuration) public view returns (uint256 amountDue) { @@ -178,7 +190,7 @@ contract Stake is IStake { amountDue = assetsBefore * pos.taxRate * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE; } - function _payTax(StakingPosition storage pos, uint256 taxFloorDuration) private { + function _payTax(uint256 positionID, StakingPosition storage pos, uint256 taxFloorDuration) private { // ihet = Implied Holding Expiry Timestamp uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration @@ -191,6 +203,7 @@ contract Stake is IStake { taxAmountDue = assetsBefore; } SafeERC20.safeTransfer(tokenContract, taxPool, taxAmountDue); + emit TaxPaid(positionID, pos.owner, taxAmountDue); if (assetsBefore - taxAmountDue > 0) { // if something left over, update storage pos.share = assetsToShares(assetsBefore - taxAmountDue, Math.Rounding.Down); @@ -199,6 +212,7 @@ contract Stake is IStake { // if nothing left over, liquidate position // TODO: emit event outstandingStake -= pos.share; + emit PositionRemoved(positionID, pos.share, pos.lastTaxTime); delete pos.owner; delete pos.creationTime; } diff --git a/onchain/test/BaseLineLP.t.sol b/onchain/test/BaseLineLP.t.sol index 6b53554..307ba16 100644 --- a/onchain/test/BaseLineLP.t.sol +++ b/onchain/test/BaseLineLP.t.sol @@ -74,7 +74,10 @@ contract BaseLineLPTest is Test { harb.setLiquidityManager(address(liquidityManager)); } - function testLP(address account, uint256 amount) public { + function testLP(address account) public { + vm.assume(account != address(0)); + vm.assume(account != address(1)); // TWAB sponsorship address + vm.assume(account != address(2)); // tax pool address vm.deal(account, 10 ether); vm.prank(account); (bool sent, ) = address(liquidityManager).call{value: 10 ether}(""); @@ -83,7 +86,6 @@ contract BaseLineLPTest is Test { vm.expectRevert(); liquidityManager.shift(); - int24 startTick = (address(weth) < address(harb)) ? int24(128219) : int24(-128219); //initialize at 1 cent liquidityManager.slide(); } } diff --git a/subgraph/harb/schema.graphql b/subgraph/harb/schema.graphql index 22c7141..5dd640f 100644 --- a/subgraph/harb/schema.graphql +++ b/subgraph/harb/schema.graphql @@ -1,6 +1,6 @@ -scalar Bytes -scalar BigInt -scalar BigDecimal +# scalar Bytes +# scalar BigInt +# scalar BigDecimal type Stats @entity { id: Bytes! @@ -25,7 +25,7 @@ type Position @entity { status: PositionStatus! } -type Query { - stats: [Stats] - positions: [Position] -} \ No newline at end of file +# type Query { +# stats: [Stats!] +# positions: [Position!] +#} \ No newline at end of file diff --git a/subgraph/harb/subgraph.yaml b/subgraph/harb/subgraph.yaml index a4a2898..a20bb33 100644 --- a/subgraph/harb/subgraph.yaml +++ b/subgraph/harb/subgraph.yaml @@ -8,9 +8,9 @@ dataSources: name: Harb network: sepolia source: - address: "0xcd02666582a2057085edabc55c5120155ba4e93c" + address: "0xcA85847c540a9706359E74155288Ab8e6b2475C7" abi: Harb - startBlock: 5510934 + startBlock: 5677168 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -32,9 +32,9 @@ dataSources: name: Stake network: sepolia source: - address: "0x4256777543814d66f9f66390f6bb33c55a24c331" + address: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8" abi: Stake - startBlock: 5510935 + startBlock: 5677168 mapping: kind: ethereum/events apiVersion: 0.0.7