diff --git a/contracts/Helpers/RouterLike.sol b/contracts/Helpers/RouterLike.sol new file mode 100644 index 0000000..9d70517 --- /dev/null +++ b/contracts/Helpers/RouterLike.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.11; + + +contract RouterLike { + SwapDescription public saved; + SwapDescription public savedSimple; + struct SwapDescription { + address srcToken; + address dstToken; + address payable srcReceiver; + address payable dstReceiver; + uint256 amount; + uint256 minReturnAmount; + uint256 flags; + bytes permit; + } + + function swap( + address caller, + SwapDescription calldata desc, + bytes calldata data + ) public { + saved = desc; + } + + function swapSimple( + SwapDescription calldata desc + ) public { + savedSimple = desc; +} +} diff --git a/tests/conftest.py b/tests/conftest.py index cbbe152..3ae54b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -96,3 +96,7 @@ def tuple_helper_yul(alice, TupleHelperYul): @pytest.fixture(scope="module") def tuple_helper_vy(alice, TupleHelperVy): yield alice.deploy(TupleHelperVy) + +@pytest.fixture(scope="module") +def router_like(alice, RouterLike): + yield alice.deploy(RouterLike) diff --git a/tests/test_chaining_actions.py b/tests/test_chaining_actions.py index 3d4b980..3cbe53d 100644 --- a/tests/test_chaining_actions.py +++ b/tests/test_chaining_actions.py @@ -27,7 +27,7 @@ def test_chaining_action(weiroll_vm, tuple_helper): w_crv_yfi_weth = WeirollContract.createContract(crv_yfi_weth) # One inch section, eth->yfi - planner.add(w_weth.approve(one_inch.address, 2**256-1)) + planner.add(w_weth.approve(one_inch.address, 2 ** 256 - 1)) swap_url = "https://api.1inch.io/v4.0/1/swap" r = requests.get( swap_url, @@ -60,11 +60,12 @@ def test_chaining_action(weiroll_vm, tuple_helper): yfi_int_amount = ReturnValue('uint256', one_inch_amount.command) # Now that we have the yfi amount, let's do curve logic - planner.add(w_weth.approve(w_curve_swap.address, 2**256-1)) - planner.add(w_yfi.approve(w_curve_swap.address, 2**256-1)) + planner.add(w_weth.approve(w_curve_swap.address, 2 ** 256 - 1)) + planner.add(w_yfi.approve(w_curve_swap.address, 2 ** 256 - 1)) + params = [Wei("5 ether"), yfi_int_amount] curve_ret = planner.add( - w_curve_swap.add_liquidity([Wei("5 ether"), yfi_int_amount], 0) + w_curve_swap.add_liquidity(params, 0) ) planner.add(w_crv_yfi_weth.transfer(w_tuple_helper.address, curve_ret)) @@ -75,3 +76,34 @@ def test_chaining_action(weiroll_vm, tuple_helper): ) assert crv_yfi_weth.balanceOf(w_tuple_helper.address) > 0 + + +def test_return_value(weiroll_vm, tuple_helper): + yfi = Contract("0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e") + crv_yfi_weth = Contract("0x29059568bB40344487d62f7450E78b8E6C74e0e5") + curve_swap = Contract("0xC26b89A667578ec7b3f11b2F98d6Fd15C07C54ba") + yfi_whale = accounts.at("0xfeb4acf3df3cdea7399794d0869ef76a6efaff52", force=True) + + # Planner and all weiroll contracts + planner = WeirollPlanner(weiroll_vm) + w_yfi = WeirollContract.createContract(yfi) + w_curve_swap = WeirollContract.createContract(curve_swap) + + yfi.transfer(weiroll_vm.address, 10 ** 18, {'from': yfi_whale}) + + w_yfi_bal = planner.call(yfi, "balanceOf", weiroll_vm.address) + # Now that we have the yfi amount, let's do curve logic + planner.add(w_yfi.approve(w_curve_swap.address, 2 ** 256 - 1)) + + params = [0, w_yfi_bal] + planner.add( + w_curve_swap.add_liquidity(params, 0) + ) + + cmds, state = planner.plan() + weiroll_tx = weiroll_vm.execute( + cmds, state, {"from": yfi_whale, "gas_limit": 8_000_000, "gas_price": 0} + ) + + assert crv_yfi_weth.balanceOf(weiroll_vm.address) > 0 + assert False diff --git a/tests/test_one_inch.py b/tests/test_one_inch.py index 590c8d6..a78c918 100644 --- a/tests/test_one_inch.py +++ b/tests/test_one_inch.py @@ -1,10 +1,15 @@ -from brownie import Contract, accounts, Wei, chain, TestableVM -from weiroll import WeirollContract, WeirollPlanner +from brownie import Contract, accounts, Wei, chain, TestableVM, convert +from brownie.convert.datatypes import HexString + +import weiroll +from weiroll import WeirollContract, WeirollPlanner, ReturnValue, CommandFlags import requests def test_one_inch(weiroll_vm): - whale = accounts.at("0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True) + whale = accounts.at( + "0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True + ) weth = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") crv = Contract("0xD533a949740bb3306d119CC777fa900bA034cd52") one_inch = Contract("0x1111111254fb6c44bAC0beD2854e76F90643097d") @@ -28,7 +33,7 @@ def test_one_inch(weiroll_vm): assert r.ok and r.status_code == 200 tx = r.json()["tx"] - weth.approve(one_inch, 2 ** 256 - 1, {"from": weiroll_vm, "gas_price": 0}) + weth.approve(one_inch, 2**256 - 1, {"from": weiroll_vm, "gas_price": 0}) decoded = one_inch.decode_input(tx["data"]) func_name = decoded[0] @@ -43,3 +48,297 @@ def test_one_inch(weiroll_vm): ) assert crv.balanceOf(weiroll_vm) > 0 + + +def test_one_inch_replace_calldata_amount(): + whale = accounts.at( + "0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True + ) + weth = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + crv = Contract("0xD533a949740bb3306d119CC777fa900bA034cd52") + one_inch = Contract("0x1111111254fb6c44bAC0beD2854e76F90643097d") + th = Contract("0xcADBA199F3AC26F67f660C89d43eB1820b7f7a3b") + ms = accounts.at("0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6", force=True) + + weth.transfer(th.address, Wei("10 ether"), {"from": whale}) + + swap_url = "https://api.1inch.io/v4.0/1/swap" + r = requests.get( + swap_url, + params={ + "fromTokenAddress": weth.address, + "toTokenAddress": crv.address, + "amount": Wei("10 ether"), + "fromAddress": th.address, + "slippage": 5, + "disableEstimate": "true", + "allowPartialFill": "false", + "usePatching": "true", + }, + ) + + assert r.ok and r.status_code == 200 + tx = r.json()["tx"] + + weth.approve(one_inch, 2**256 - 1, {"from": th, "gas_price": 0}) + + decoded = one_inch.decode_input(tx["data"]) + func_name = decoded[0] + params = decoded[1] + + # change inputs + wei9 = Wei("9 ether") + params[1][4] = wei9 + params[1][5] = 1 + + planner = WeirollPlanner(th) + # w_one_inch = weiroll.WeirollContract(one_inch) + # print(f'{func_name}\n{params}') + # planner.add(w_one_inch.swap(params[0], params[1], params[2])) + planner.call(one_inch, func_name, *params) + + cmds, state = planner.plan() + print(f"cmds: {cmds}") + print(f"state: {state}") + + weiroll_tx = th.execute( + cmds, state, {"from": ms, "gas_limit": 8_000_000, "gas_price": 0} + ) + + assert crv.balanceOf(th) > 0 + print(crv.balanceOf(th)) + assert weth.balanceOf(th) == 1 * 10**18 + assert False + + +def test_one_inch_replace_calldata_amount_2(weiroll_vm): + whale = accounts.at( + "0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True + ) + weth = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + crv = Contract("0xD533a949740bb3306d119CC777fa900bA034cd52") + one_inch = Contract("0x1111111254fb6c44bAC0beD2854e76F90643097d") + + weth.transfer(weiroll_vm.address, Wei("10 ether"), {"from": whale}) + + swap_url = "https://api.1inch.io/v4.0/1/swap" + r = requests.get( + swap_url, + params={ + "fromTokenAddress": weth.address, + "toTokenAddress": crv.address, + "amount": Wei("10 ether"), + "fromAddress": weiroll_vm.address, + "slippage": 5, + "disableEstimate": "true", + "allowPartialFill": "false", + }, + ) + + assert r.ok and r.status_code == 200 + tx = r.json()["tx"] + + weth.approve(one_inch, 2**256 - 1, {"from": weiroll_vm, "gas_price": 0}) + + decoded = one_inch.decode_input(tx["data"]) + func_name = decoded[0] + params = decoded[1] + calldata = params[2] + + # change inputs + wei9 = Wei("9 ether") + + # testing with calldata amount < struct amount at 10 wei still + # params[1][4] = wei9 + params[1][5] = 1 + + # Operations to edit decoded swap data + # convert dec to hex (0xHexDec) -> (0x00...HexDec) -> (00..HexDec) string + hex10 = HexString(hex(Wei("10 ether")), "bytes32").hex() + hex9 = HexString(hex(wei9), "bytes32").hex() + + # HexString to string + calldata_str = calldata.hex() + + # string replacement + replaced = calldata_str.replace(hex10, hex9) + + # back to hexstring + params[2] = HexString(replaced, "bytes") + + planner = WeirollPlanner(weiroll_vm) + planner.call(one_inch, func_name, *params) + + cmds, state = planner.plan() + weiroll_tx = weiroll_vm.execute( + cmds, state, {"from": whale, "gas_limit": 8_000_000, "gas_price": 0} + ) + + assert crv.balanceOf(weiroll_vm) > 0 + print(crv.balanceOf(weiroll_vm)) + + +import re +import eth_abi +from brownie import Contract, accounts, Wei, chain, TestableVM, convert +from brownie.convert.datatypes import HexString +import weiroll +from weiroll import WeirollContract, WeirollPlanner, ReturnValue +import requests + +# def test_a_b(): +# cmds, staste = test_one_inch_replace_calldata_with_weiroll(True) +# cmds2, staste2 = test_one_inch_replace_calldata_with_weiroll(False) +# +# assert False + + +def test_one_inch_replace_amount_with_weiroll(router_like): + whale = accounts.at( + "0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True + ) + weth = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + crv = Contract("0xD533a949740bb3306d119CC777fa900bA034cd52") + one_inch = Contract("0x1111111254fb6c44bAC0beD2854e76F90643097d") + tuple_helper = Contract("0x06706E366159cEfD3789184686da5cC3f47fB4a2") + w_tuple_helper = weiroll.WeirollContract(tuple_helper, CommandFlags.CALL) + th = Contract("0xcADBA199F3AC26F67f660C89d43eB1820b7f7a3b") + ms = accounts.at("0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6", force=True) + planner = WeirollPlanner(th) + + weth.approve(th.address, Wei("9 ether"), {"from": whale}) + weth.transfer(th.address, Wei("9 ether"), {"from": whale}) + + w_weth_balance = planner.call(weth, "balanceOf", th.address) + w_weth_balance = weiroll.ReturnValue("bytes32", w_weth_balance.command) + swap_url = "https://api.1inch.io/v4.0/1/swap" + print(th.address) + r = requests.get( + swap_url, + params={ + "fromTokenAddress": weth.address, + "toTokenAddress": crv.address, + "amount": Wei("10 ether"), + "fromAddress": th.address, + "slippage": 5, + "disableEstimate": "true", + "allowPartialFill": "false", + "usePatching": "true", + }, + ) + + assert r.ok and r.status_code == 200 + tx = r.json()["tx"] + router = tx["to"] + print(f"router: {router}") + weth.approve(router, 0, {"from": th, "gas_price": 0}) + weth.approve(router, 2**256 - 1, {"from": th, "gas_price": 0}) + + decoded = one_inch.decode_input(tx["data"]) + func_name = decoded[0] + params = decoded[1] + params[1][5] = 1 # minReturn + + struct_layout = ( + "(address,address,address,address,uint256,uint256,uint256,bytes)" + ) + tuple_bytes = eth_abi.encode_single(struct_layout, params[1]) + tuple_raw = planner.add( + w_tuple_helper.replaceElement( + tuple_bytes, 4, w_weth_balance, True + ).rawValue() + ) + + tuple_raw = weiroll.ReturnValue( + struct_layout, tuple_raw.command + ) + params[1] = tuple_raw + + print(params) + + w_router = weiroll.WeirollContract.createContract(router_like) + planner.call(router_like, "swapSimple", params[1]) + # planner.add(w_router.swap(params[0], params[1], params[2])) + + cmds, state = planner.plan() + + weiroll_tx = th.execute( + cmds, state, {"from": ms, "gas_limit": 8_000_000, "gas_price": 0} + ) + + assert crv.balanceOf(th) > 0 + print(crv.balanceOf(th)) + assert False + +def test_tuple_replace(): + whale = accounts.at( + "0x57757E3D981446D585Af0D9Ae4d7DF6D64647806", force=True + ) + weth = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + crv = Contract("0xD533a949740bb3306d119CC777fa900bA034cd52") + tuple_helper = Contract("0x06706E366159cEfD3789184686da5cC3f47fB4a2") + w_tuple_helper = weiroll.WeirollContract(tuple_helper) + th = Contract("0xcADBA199F3AC26F67f660C89d43eB1820b7f7a3b") + ms = accounts.at("0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6", force=True) + planner = WeirollPlanner(th) + + weth_balance_whale = weth.balanceOf(whale) + + # transfer all of whale's weth to th + weth.approve(th.address, 2**256 - 1, {"from": whale}) + weth.transfer(th.address, weth.balanceOf(whale), {"from": whale}) + + # get weiroll amount + w_weth_balance = planner.call(weth, "balanceOf", th.address) + w_weth_balance = weiroll.ReturnValue("bytes32", w_weth_balance.command) + + tuple = [ + crv.address, + crv.address, + crv.address, + crv.address, + 999, + 1, + 999, + b"0x0", + ] + struct_layout = ( + "(address,address,address,address,uint256,uint256,uint256,bytes)" + ) + tuple_bytes = eth_abi.encode_single(struct_layout, tuple) + + # replace amount with w_amount + tuple_description_bytes = planner.add( + w_tuple_helper.replaceElement( + tuple_bytes, 4, w_weth_balance, True + ).rawValue() + ) + + print(tuple_description_bytes) + + tuple_description = weiroll.ReturnValue( + struct_layout, tuple_description_bytes.command + ) + + # do some transfers in order to verify amount was correctly replaced + actual_balance = planner.add( + w_tuple_helper.getElement(tuple_description_bytes, 4) + ) + actual_minReturn = planner.add( + w_tuple_helper.getElement(tuple_description_bytes, 5) + ) + actual_balance = weiroll.ReturnValue("uint256", actual_balance.command) + actual_minReturn = weiroll.ReturnValue("uint256", actual_minReturn.command) + planner.call(weth, "transfer", ms.address, actual_balance) + planner.call(crv, "approve", ms.address, actual_minReturn) + + cmds, state = planner.plan() + + weth_balance_ms = weth.balanceOf(ms) + weiroll_tx = th.execute( + cmds, state, {"from": ms, "gas_limit": 8_000_000, "gas_price": 0} + ) + + # assert replacing amount w/ w_amount worked + assert weth.balanceOf(ms) - weth_balance_ms == weth_balance_whale + assert crv.allowance(th, ms) == 1 diff --git a/tests/test_swaps.py b/tests/test_swaps.py index a52ab8d..e61375b 100644 --- a/tests/test_swaps.py +++ b/tests/test_swaps.py @@ -83,7 +83,6 @@ def test_swaps(accounts, weiroll_vm): ) -@pytest.mark.skip("broken") def test_balancer_swap(accounts, weiroll_vm, tuple_helper): bal_whale = accounts.at("0xF977814e90dA44bFA03b6295A0616a897441aceC", force=True) diff --git a/tests/test_weiroll.py b/tests/test_weiroll.py index 8e178e0..961482f 100644 --- a/tests/test_weiroll.py +++ b/tests/test_weiroll.py @@ -52,7 +52,6 @@ def test_weiroll_planner_simple_program(alice, math): assert state[0] == eth_abi.encode_single("uint", 1) assert state[1] == eth_abi.encode_single("uint", 2) - def test_weiroll_deduplicates_identical_literals(alice, math): planner = weiroll.WeirollPlanner(alice) planner.add(math.add(1, 1))