diff --git a/foundry/balance-drop-trap/foundry.toml b/foundry/balance-drop-trap/foundry.toml new file mode 100644 index 0000000..4b65823 --- /dev/null +++ b/foundry/balance-drop-trap/foundry.toml @@ -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/' +] diff --git a/foundry/balance-drop-trap/src/BalanceDropTrap.sol b/foundry/balance-drop-trap/src/BalanceDropTrap.sol new file mode 100644 index 0000000..39d3be8 --- /dev/null +++ b/foundry/balance-drop-trap/src/BalanceDropTrap.sol @@ -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("")); + } +} diff --git a/foundry/balance-drop-trap/test/BalanceDropTrap.t.sol b/foundry/balance-drop-trap/test/BalanceDropTrap.t.sol new file mode 100644 index 0000000..a0bfd39 --- /dev/null +++ b/foundry/balance-drop-trap/test/BalanceDropTrap.t.sol @@ -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"); + } +}