Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions packages/evm-contracts/contracts/perps/AgentPerpEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ contract AgentPerpEngine is AccessControl, ReentrancyGuard {
uint256 public immutable defaultSkewScale;
bool public tradingPaused;
bool public marketCreationPaused;
bool private _isLiquidationContext;

event MarketCreated(
bytes32 indexed agentId,
Expand Down Expand Up @@ -540,9 +539,7 @@ contract AgentPerpEngine is AccessControl, ReentrancyGuard {
Position storage position = positions[agentId][trader];
if (position.size == 0) revert NoPosition();

_isLiquidationContext = true;
_syncOracle(agentId);
_isLiquidationContext = false;
int256 fundingPayment = _settleFunding(position, market, true);

uint256 markPrice = _markPrice(market, config);
Expand Down Expand Up @@ -814,7 +811,7 @@ contract AgentPerpEngine is AccessControl, ReentrancyGuard {

(uint256 mu, uint256 sigma, uint256 lastUpdate) = oracle.agentSkills(agentId);
if (lastUpdate == 0) revert UnknownOracleAgent();
if (!_isLiquidationContext && block.timestamp - lastUpdate > config.maxOracleDelay) revert StaleOracle();
if (block.timestamp - lastUpdate > config.maxOracleDelay) revert StaleOracle();

_accrueFunding(market, config);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ contract AgentPerpEngineNative is AccessControl, ReentrancyGuard {
uint256 public immutable fundingVelocity;
uint256 public immutable maxLeverage;
bool public tradingPaused;
bool private _isLiquidationContext;

event PositionOpened(
bytes32 indexed agentId,
Expand Down Expand Up @@ -249,9 +248,9 @@ contract AgentPerpEngineNative is AccessControl, ReentrancyGuard {
}

MarketConfig memory config = marketConfigs[agentId];
(uint256 mu,, uint256 lastUpdate) = oracle.agentSkills(agentId);
(,, uint256 lastUpdate) = oracle.agentSkills(agentId);
if (lastUpdate == 0) revert UnknownOracleAgent();
if (!_isLiquidationContext && block.timestamp - lastUpdate > config.maxOracleDelay) revert StaleOracle();
if (block.timestamp - lastUpdate > config.maxOracleDelay) revert StaleOracle();

_updateFunding(agentId);
price = oracle.getIndexPrice(agentId);
Expand Down Expand Up @@ -536,9 +535,7 @@ contract AgentPerpEngineNative is AccessControl, ReentrancyGuard {

function liquidate(bytes32 agentId, address trader) external nonReentrant {
if (!marketConfigs[agentId].exists) revert MarketNotFound();
_isLiquidationContext = true;
_syncOracle(agentId);
_isLiquidationContext = false;

MarketConfig memory config = marketConfigs[agentId];
MarketState storage market = markets[agentId];
Expand Down
66 changes: 66 additions & 0 deletions packages/evm-contracts/test/perps/AgentPerpEngine.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../../contracts/perps/SkillOracle.sol";
import "../../contracts/perps/AgentPerpEngine.sol";
import "../../contracts/perps/AgentPerpEngineNative.sol";
import "../../contracts/MockERC20.sol";

contract AgentPerpEngineTest is Test {
Expand Down Expand Up @@ -94,6 +95,71 @@ contract AgentPerpEngineTest is Test {
engine.syncOracle(agentId);
}

function testOracleStalenessBlocksLiquidation() public {
bytes32 staleAgent = keccak256("STALE_AGENT");
bytes32 peerAgent = keccak256("STALE_PEER");

vm.prank(admin);
oracle.updateAgentSkill(staleAgent, 1500, 0);
vm.prank(admin);
oracle.updateAgentSkill(peerAgent, 1500, 0);

vm.prank(operator);
engine.createMarket(
staleAgent,
1_000_000 * 1e18,
5 * 1e18,
1_000,
500,
1 minutes,
0,
0,
0
);

vm.prank(alice);
engine.modifyPosition(staleAgent, 100 * 1e18, 4 * 1e18);

vm.prank(admin);
oracle.updateAgentSkill(staleAgent, 1000, 0);

vm.warp(block.timestamp + 1 minutes + 1);

vm.prank(bob);
vm.expectRevert(AgentPerpEngine.StaleOracle.selector);
engine.liquidate(staleAgent, alice);
}

function testNativeOracleStalenessBlocksLiquidation() public {
bytes32 staleAgent = keccak256("NATIVE_STALE_AGENT");
bytes32 peerAgent = keccak256("NATIVE_STALE_PEER");

SkillOracle nativeOracle = new SkillOracle(100 * 1e18, 2 hours, admin, admin, pauser);
vm.prank(admin);
nativeOracle.updateAgentSkill(staleAgent, 1500, 0);
vm.prank(admin);
nativeOracle.updateAgentSkill(peerAgent, 1500, 0);

AgentPerpEngineNative nativeEngine =
new AgentPerpEngineNative(nativeOracle, 1_000_000 * 1e18, admin, operator, pauser);

vm.prank(operator);
nativeEngine.createMarket(staleAgent);

vm.deal(alice, 1_000 ether);
vm.prank(alice);
nativeEngine.modifyPosition{value: 100 ether}(staleAgent, 4 * 1e18);

vm.prank(admin);
nativeOracle.updateAgentSkill(staleAgent, 1000, 0);

vm.warp(block.timestamp + nativeEngine.DEFAULT_MAX_ORACLE_DELAY() + 1);

vm.prank(bob);
vm.expectRevert(AgentPerpEngineNative.StaleOracle.selector);
nativeEngine.liquidate(staleAgent, alice);
}

function testOracleStalenessBlocksGetIndexPrice() public {
vm.warp(block.timestamp + 3 minutes);
vm.expectRevert(SkillOracle.StaleOracle.selector);
Expand Down
Loading