Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Mask address args in STL #347

Merged
merged 3 commits into from
Jun 13, 2023
Merged

🐛 Mask address args in STL #347

merged 3 commits into from
Jun 13, 2023

Conversation

transmissions11
Copy link
Owner

Description

Should close the following issues by properly masking address arguments in STL:

#346
#344
#299

Checklist

Ensure you completed all of the steps below before submitting your pull request:

  • Ran forge snapshot?
  • Ran npm run lint?
  • Ran forge test?

Pull requests with an incomplete checklist will be thrown out.

@sambacha
Copy link
Contributor

what additional tests would you want? (if any)

@transmissions11
Copy link
Owner Author

#299

basically these but + fuzz test versions ideally

@sandybradley
Copy link

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "solmate/utils/SafeTransferLib.sol";
import "solmate/tokens/ERC20.sol";

contract ContractTest is Test {

    Token token = new Token();
    Router router = new Router();

    function setUp() public {
        
    }
    function testSafeTransferOk(uint256 amount) public {
        token.approve(address(router), amount);
        router.safeTransferSucceeds(address(token), amount); // Ok.
    }
    function testSafeTransferNok(uint256 amount) public {
        token.approve(address(router), amount);
        router.safeTransferFails(address(token), amount); // This fails.
    }
}

contract Router {
    function safeTransferSucceeds(address token, uint256 amount) public {
        address to = address(uint160(uint256(0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff))); // type(uint160).max
        SafeTransferLib.safeTransferFrom(ERC20(token), msg.sender, to, amount);
    }
    function safeTransferFails(address token, uint256 amount) public {
        address to = address(uint160(uint256(0x0000000000000000000000010000000000000000000000000000000000000000))); // type(uint160).max + 1
        SafeTransferLib.safeTransferFrom(ERC20(token), msg.sender, to, amount);
    }
}

contract Token is ERC20("", "", 18) {
    constructor() {
        _mint(msg.sender, type(uint256).max);
    }
}
Running 2 tests for test/SafeTransferLib.t.sol:ContractTest
[PASS] testSafeTransferNok(uint256) (runs: 256, μ: 47911, ~: 48612)
Traces:
  [52879] ContractTest::testSafeTransferNok(2)
    ├─ [24523] Token::approve(Router: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 2)
    │   ├─ emit Approval(owner: ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], spender: Router: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], amount: 2)
    │   └─ ← true
    ├─ [24851] Router::safeTransferFails(Token: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 2)
    │   ├─ [24343] Token::transferFrom(ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x0000000000000000000000000000000000000000, 2)
    │   │   ├─ emit Transfer(from: ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], to: 0x0000000000000000000000000000000000000000, amount: 2)
    │   │   └─ ← true
    │   └─ ← ()
    └─ ← ()

[PASS] testSafeTransferOk(uint256) (runs: 256, μ: 47966, ~: 48566)
Traces:
  [52831] ContractTest::testSafeTransferOk(1889567281)
    ├─ [24523] Token::approve(Router: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 1889567281)
    │   ├─ emit Approval(owner: ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], spender: Router: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], amount: 1889567281)
    │   └─ ← true
    ├─ [24847] Router::safeTransferSucceeds(Token: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 1889567281)
    │   ├─ [24343] Token::transferFrom(ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, 1889567281)
    │   │   ├─ emit Transfer(from: ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], to: 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, amount: 1889567281)
    │   │   └─ ← true
    │   └─ ← ()
    └─ ← ()

Test result: ok. 2 passed; 0 failed; finished in 3.59s

@sandybradley
Copy link

Or something more complete:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "solmate/utils/SafeTransferLib.sol";
import "solmate/tokens/ERC20.sol";

contract SafeTransferLibTest is Test {
    using SafeTransferLib for ERC20;

    ERC20 token;
    address user = 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926;

    function setUp() public {
        vm.prank(user);
        token = new Token();
    }

    function testSafeTransferFrom(address to, uint256 amount) public {
        vm.prank(user);
        ERC20(token).safeApprove(address(this), amount);
        ERC20(token).safeTransferFrom(user, to, amount);
    }

    function testSafeTransfer(address to, uint256 amount) public {
        vm.prank(user);
        ERC20(token).safeTransfer(to, amount);
    }

    function testSafeTransferETH(uint256 amount) public {
        vm.assume(amount < type(uint256).max / 2);
        vm.deal(user, amount);
        vm.prank(user);
        SafeTransferLib.safeTransferETH(address(this), amount);
    }

    receive() external payable {}
    fallback() external payable {}
}

contract Token is ERC20("", "", 18) {
    constructor() {
        _mint(msg.sender, type(uint256).max);
    }
}
Running 3 tests for test/SafeTransferLib.t.sol:SafeTransferLibTest
[PASS] testSafeTransfer(address,uint256) (runs: 256, μ: 39047, ~: 39837)
Traces:
  [39837] SafeTransferLibTest::testSafeTransfer(0xf497BA995A0d145956eBb6358782F60D83556F37, 2)
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [29674] Token::transfer(0xf497BA995A0d145956eBb6358782F60D83556F37, 2)
    │   ├─ emit Transfer(from: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, to: 0xf497BA995A0d145956eBb6358782F60D83556F37, amount: 2)
    │   └─ ← true
    └─ ← ()

[PASS] testSafeTransferETH(uint256) (runs: 256, μ: 12749, ~: 13090)
Traces:
  [13090] SafeTransferLibTest::testSafeTransferETH(33194644780465427076338445808891870097632030972)
    ├─ [0] VM::assume(true) [staticcall]
    │   └─ ← ()
    ├─ [0] VM::deal(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, 33194644780465427076338445808891870097632030972)
    │   └─ ← ()
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [55] SafeTransferLibTest::receive{value: 33194644780465427076338445808891870097632030972}()
    │   └─ ← ()
    └─ ← ()

[PASS] testSafeTransferFrom(address,uint256) (runs: 256, μ: 47561, ~: 48264)
Traces:
  [52578] SafeTransferLibTest::testSafeTransferFrom(0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, 2265)
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [24523] Token::approve(SafeTransferLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 2265)
    │   ├─ emit Approval(owner: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, spender: SafeTransferLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], amount: 2265)
    │   └─ ← true
    ├─ [24343] Token::transferFrom(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, 0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, 2265)
    │   ├─ emit Transfer(from: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, to: 0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, amount: 2265)
    │   └─ ← true
    └─ ← ()

Test result: ok. 3 passed; 0 failed; finished in 2.77s

@transmissions11
Copy link
Owner Author

Or something more complete:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "solmate/utils/SafeTransferLib.sol";
import "solmate/tokens/ERC20.sol";

contract SafeTransferLibTest is Test {
    using SafeTransferLib for ERC20;

    ERC20 token;
    address user = 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926;

    function setUp() public {
        vm.prank(user);
        token = new Token();
    }

    function testSafeTransferFrom(address to, uint256 amount) public {
        vm.prank(user);
        ERC20(token).safeApprove(address(this), amount);
        ERC20(token).safeTransferFrom(user, to, amount);
    }

    function testSafeTransfer(address to, uint256 amount) public {
        vm.prank(user);
        ERC20(token).safeTransfer(to, amount);
    }

    function testSafeTransferETH(uint256 amount) public {
        vm.assume(amount < type(uint256).max / 2);
        vm.deal(user, amount);
        vm.prank(user);
        SafeTransferLib.safeTransferETH(address(this), amount);
    }

    receive() external payable {}
    fallback() external payable {}
}

contract Token is ERC20("", "", 18) {
    constructor() {
        _mint(msg.sender, type(uint256).max);
    }
}
Running 3 tests for test/SafeTransferLib.t.sol:SafeTransferLibTest
[PASS] testSafeTransfer(address,uint256) (runs: 256, μ: 39047, ~: 39837)
Traces:
  [39837] SafeTransferLibTest::testSafeTransfer(0xf497BA995A0d145956eBb6358782F60D83556F37, 2)
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [29674] Token::transfer(0xf497BA995A0d145956eBb6358782F60D83556F37, 2)
    │   ├─ emit Transfer(from: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, to: 0xf497BA995A0d145956eBb6358782F60D83556F37, amount: 2)
    │   └─ ← true
    └─ ← ()

[PASS] testSafeTransferETH(uint256) (runs: 256, μ: 12749, ~: 13090)
Traces:
  [13090] SafeTransferLibTest::testSafeTransferETH(33194644780465427076338445808891870097632030972)
    ├─ [0] VM::assume(true) [staticcall]
    │   └─ ← ()
    ├─ [0] VM::deal(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, 33194644780465427076338445808891870097632030972)
    │   └─ ← ()
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [55] SafeTransferLibTest::receive{value: 33194644780465427076338445808891870097632030972}()
    │   └─ ← ()
    └─ ← ()

[PASS] testSafeTransferFrom(address,uint256) (runs: 256, μ: 47561, ~: 48264)
Traces:
  [52578] SafeTransferLibTest::testSafeTransferFrom(0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, 2265)
    ├─ [0] VM::prank(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926)
    │   └─ ← ()
    ├─ [24523] Token::approve(SafeTransferLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 2265)
    │   ├─ emit Approval(owner: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, spender: SafeTransferLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], amount: 2265)
    │   └─ ← true
    ├─ [24343] Token::transferFrom(0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, 0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, 2265)
    │   ├─ emit Transfer(from: 0xA3A771A7c4AFA7f0a3f88Cc6512542241851C926, to: 0x08198855A1FcAdC0543b58D29c141A5F9e0C9292, amount: 2265)
    │   └─ ← true
    └─ ← ()

Test result: ok. 3 passed; 0 failed; finished in 2.77s

did these pass before these changes? we already have tests for this file fyi

@sandybradley
Copy link

My bad. The test below fails with the old version but passes with the new.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "solmate/utils/SafeTransferLib.sol";
import "solmate/tokens/ERC20.sol";

contract SafeTransferLibTest is Test {
    using SafeTransferLib for ERC20;
    using stdStorage for StdStorage;

    address FRAX = 0x853d955aCEf822Db058eb8505911ED77F175b99e;

    function writeTokenBalance(
        address who,
        address token,
        uint256 amt
    ) internal {
        stdstore.target(token).sig(ERC20(token).balanceOf.selector).with_key(who).checked_write(amt);
    }

    function testSafeTransferFrax(uint256 to, uint256 amount) public {
        vm.assume(to > 0);
        vm.assume(to != 1461501637330902918203684832716283019655932542976);
        vm.assume(amount > 0);
        vm.assume(amount < type(uint256).max / 2);
        address _to;
        assembly ("memory-safe") {
            let pos := mload(0x40)
            mstore(pos, to)
            _to := mload(pos)
        }
        writeTokenBalance(address(this), FRAX, amount);
        ERC20(FRAX).safeTransfer(_to, amount);
    }
}

Old SafeTransferLib

[376] 0x853d955aCEf822Db058eb8505911ED77F175b99e::transfer(0x0000000000000000000000000000000000000001, 1)
└─ ← "EvmError: Revert"

New SafeTransferLib

[20339] 0x853d955aCEf822Db058eb8505911ED77F175b99e::transfer(0x43c3eedE6E419Fa1B8A71781afd1B9ADd0e96cB0, 64021726494435774418933814372073409007200368138728157)
├─ emit Transfer(param0: SafeTransferLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], param1: 0x43c3eedE6E419Fa1B8A71781afd1B9ADd0e96cB0, param2: 64021726494435774418933814372073409007200368138728157)
└─ ← 0x0000000000000000000000000000000000000000000000000000000000000001

@transmissions11
Copy link
Owner Author

Need to implement regressions tests for this at some point but have delayed merging this for far too long.

@transmissions11 transmissions11 merged commit 50e15bb into main Jun 13, 2023
@sambacha
Copy link
Contributor

Need to implement regressions tests for this at some point but have delayed merging this for far too long.

v7 release soon?

What else ya need to push it over the line for that?

Thanks for following up, let us know how we can help, cheers.

@transmissions11
Copy link
Owner Author

transmissions11 commented Jun 13, 2023

v7 release soon?

tbqh ive lied about this so many times i dont even trust myself to give a timeline here anymore, but i'd like to get it wrapped up within the next month or two yea

in terms of what it takes to get over the line its mostly converting the suite away from dstest -> foundry modernities w/ forge-std + merging the backlog of PRs + auditing + incorporating optimization SoTA

@sambacha
Copy link
Contributor

v7 release soon?

tbqh ive lied about this so many times i dont even trust myself to give a timeline here anymore, but i'd like to get it wrapped up within the next month or two yea

Ahh so obviously to be bundled with Foundry v1.0 got ya

@transmissions11
Copy link
Owner Author

earnestly not the intent but that would be nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants