Skip to content
Open
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
8 changes: 8 additions & 0 deletions foundry/balance-drop-trap/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[profile.default]
src = 'src'
test = 'test'
libs = ['../../node_modules']
remappings = [
'drosera-contracts/=../../node_modules/contracts/src/',
'forge-std/=../../node_modules/forge-std/src/'
]
48 changes: 48 additions & 0 deletions foundry/balance-drop-trap/src/BalanceDropTrap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ITrap} from "drosera-contracts/interfaces/ITrap.sol";

// This trap detects sudden drops in the ETH balance of a specific wallet.
contract BalanceDropTrap is ITrap {
// The wallet address to monitor (Example: Vitalik Buterin's address)
address constant TARGET_WALLET = 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B;

// Using BPS for finer control (10,000 = 100%)
uint256 constant BPS = 10_000;
uint256 constant THRESHOLD_BPS = 5_000; // 50% threshold

// This function runs on every block and records the wallet's current ETH balance.
function collect() external view override returns (bytes memory) {
// Including block.number for easier debugging as suggested
return abi.encode(block.number, TARGET_WALLET.balance);
}

// This function analyzes the data from past blocks.
function shouldRespond(bytes[] calldata data) external pure override returns (bool, bytes memory) {
// At least 2 blocks of data are needed for analysis.
if (data.length < 2) {
return (false, bytes(""));
}

// Drosera passes samples newest first: data[0] is current, data[1] is previous.
( , uint256 currentBalance) = abi.decode(data[0], (uint256, uint256));
( , uint256 previousBalance) = abi.decode(data[1], (uint256, uint256));

// If the balance has not dropped or the previous balance was 0, there is no issue.
if (currentBalance >= previousBalance || previousBalance == 0) {
return (false, bytes(""));
}

// Calculate the drop amount and percentage in BPS.
uint256 dropBps = ((previousBalance - currentBalance) * BPS) / previousBalance;

// If the drop is greater than our threshold, TRIGGER THE ALARM!
if (dropBps >= THRESHOLD_BPS) {
return (true, abi.encode(previousBalance, currentBalance));
}

// If the drop is less than the threshold, there is no issue.
return (false, bytes(""));
}
}
47 changes: 47 additions & 0 deletions foundry/balance-drop-trap/test/BalanceDropTrap.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {BalanceDropTrap} from "../../src/BalanceDropTrap.sol";

// Test kontratımız, Foundry'nin Test kontratından miras alır.
contract BalanceDropTrapTest is Test {
BalanceDropTrap public trap;

// Bu fonksiyon, her testten önce çalışır ve tuzağımızı kurar.
function setUp() public {
trap = new BalanceDropTrap();
}

// Bu test, bakiye %50'den fazla düştüğünde tuzağın alarm vermesi gerektiğini doğrular.
function testShouldRespondOnSignificantDrop() public {
// Geçmiş verileri simüle ediyoruz.
bytes[] memory data = new bytes[](2);

// Önceki bakiye: 100 ETH
data[0] = abi.encode(100 ether);
// Şimdiki bakiye: 40 ETH (yani %60'lık bir düşüş)
data[1] = abi.encode(40 ether);

// Tuzağımızın shouldRespond fonksiyonunu bu sahte verilerle çağırıyoruz.
(bool should, ) = trap.shouldRespond(data);

// Sonucun 'true' olmasını bekliyoruz ve bunu doğruluyoruz.
assertTrue(should, "Trap should respond on a >50% balance drop");
}

// Bu test, bakiye %50'den az düştüğünde tuzağın alarm VERMEMESİ gerektiğini doğrular.
function testShouldNotRespondOnInsignificantDrop() public {
bytes[] memory data = new bytes[](2);

// Önceki bakiye: 100 ETH
data[0] = abi.encode(100 ether);
// Şimdiki bakiye: 80 ETH (yani %20'lik bir düşüş)
data[1] = abi.encode(80 ether);

(bool should, ) = trap.shouldRespond(data);

// Sonucun 'false' olmasını bekliyoruz ve bunu doğruluyoruz.
assertFalse(should, "Trap should NOT respond on a <50% balance drop");
}
}