From c0dad2efcf3921b03666773616008cd4c5d6f3c1 Mon Sep 17 00:00:00 2001 From: Xiangan He <76530366+xBalbinus@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:28:06 -0500 Subject: [PATCH] reintroduce delegateCall; ofac/audit; merge tips + callbreaker; [wip] existing examples + tests improvements (#37) * reintroduce delegateCall; ofac/audit; merge tips + callbreaker * completed PnP exmaple --- src/TimeTypes.sol | 1 + src/lamination/LaminatedProxy.sol | 9 +- src/timetravel/CallBreaker.sol | 23 ++++- src/timetravel/SmarterContract.sol | 53 ++++++++++- src/tips/Tips.sol | 28 ------ test/CallBreaker.t.sol | 4 - test/GasSnapshot.t.sol | 1 + test/Laminator.t.sol | 42 +++++--- test/NoopTurnerTest.t.sol | 3 +- test/PnP.t.sol | 55 +++++++++++ test/Whitelist.t.sol | 82 ++++++++++++++++ test/examples/Caller.sol | 31 ++++++ test/examples/FlashPill.sol | 3 +- test/examples/LimitOrder.sol | 3 +- test/examples/NoopTurner.sol | 17 ++-- test/examples/PnP.sol | 36 ++++--- test/examples/SelfCheckout.sol | 3 +- test/solve-lib/CronTwo.sol | 21 ++-- test/solve-lib/PnPExample.sol | 89 +++++++++++++++++ test/solve-lib/Whitelist.sol | 148 +++++++++++++++++++++++++++++ test/solve-lib/WorkedExample.sol | 21 ++-- 21 files changed, 575 insertions(+), 98 deletions(-) delete mode 100644 src/tips/Tips.sol create mode 100644 test/PnP.t.sol create mode 100644 test/Whitelist.t.sol create mode 100644 test/examples/Caller.sol create mode 100644 test/solve-lib/PnPExample.sol create mode 100644 test/solve-lib/Whitelist.sol diff --git a/src/TimeTypes.sol b/src/TimeTypes.sol index b3b2228..17e10ec 100644 --- a/src/TimeTypes.sol +++ b/src/TimeTypes.sol @@ -11,6 +11,7 @@ struct CallObject { uint256 gas; address addr; bytes callvalue; + bool delegate; } /// @dev Struct for holding a CallObject with an associated index diff --git a/src/lamination/LaminatedProxy.sol b/src/lamination/LaminatedProxy.sol index f9f7596..04419ef 100644 --- a/src/lamination/LaminatedProxy.sol +++ b/src/lamination/LaminatedProxy.sol @@ -240,8 +240,13 @@ contract LaminatedProxy is LaminatedStorage, ReentrancyGuard { bool success; bytes memory returnvalue; - (success, returnvalue) = - callToMake.addr.call{gas: callToMake.gas, value: callToMake.amount}(callToMake.callvalue); + if (callToMake.delegate) { + (success, returnvalue) = callToMake.addr.delegatecall(callToMake.callvalue); + } else { + (success, returnvalue) = + callToMake.addr.call{gas: callToMake.gas, value: callToMake.amount}(callToMake.callvalue); + } + if (!success) { revert CallFailed(); } diff --git a/src/timetravel/CallBreaker.sol b/src/timetravel/CallBreaker.sol index 2a3eb61..a833a75 100644 --- a/src/timetravel/CallBreaker.sol +++ b/src/timetravel/CallBreaker.sol @@ -45,6 +45,8 @@ contract CallBreaker is CallBreakerStorage { /// @param index The index of the return value in the returnStore event EnterPortal(CallObject callObj, ReturnObject returnvalue, uint256 index); + event Tip(address indexed from, address indexed to, uint256 amount); + /// @notice Emitted when the verifyStxn function is called event VerifyStxn(); @@ -55,6 +57,15 @@ contract CallBreaker is CallBreakerStorage { _setPortalClosed(); } + /// @dev Tips should be transferred from each LaminatorProxy to the solver via msg.value + receive() external payable { + bytes32 tipAddrKey = keccak256(abi.encodePacked("tipYourBartender")); + bytes memory tipAddrBytes = fetchFromAssociatedDataStore(tipAddrKey); + address tipAddr = abi.decode(tipAddrBytes, (address)); + emit Tip(msg.sender, tipAddr, msg.value); + payable(tipAddr).transfer(msg.value); + } + /// @dev Modifier to make a function callable only when the portal is open. /// Reverts if the portal is closed. Portal is opened by `verify`. modifier ensureTurnerOpen() { @@ -103,7 +114,7 @@ contract CallBreaker is CallBreakerStorage { emit VerifyStxn(); } - /// @notice Executes a call and returns a value from the record of return values. + /// @notice Returns a value from the record of return values from the callObject. /// @dev This function also does some accounting to track the occurrence of a given pair of call and return values. /// @param input The call to be executed, structured as a CallObjectWithIndex. /// @return The return value from the record of return values. @@ -114,6 +125,16 @@ contract CallBreaker is CallBreakerStorage { return thisReturn.returnvalue; } + /// @notice Gets a return value from the record of return values from the index number. + /// @dev This function also does some accounting to track the occurrence of a given pair of call and return values. + /// @param index The call to be executed, structured as a CallObjectWithIndex. + /// @return The return value from the record of return values. + function getReturnValue(uint256 index) external view returns (bytes memory) { + // Decode the input to obtain the CallObject and calculate a unique ID representing the call-return pair + ReturnObject memory thisReturn = _getReturn(index); + return thisReturn.returnvalue; + } + /// @notice Fetches the value associated with a given key from the associatedDataStore /// @param key The key whose associated value is to be fetched /// @return The value associated with the given key diff --git a/src/timetravel/SmarterContract.sol b/src/timetravel/SmarterContract.sol index 5329fe0..bf05a6b 100644 --- a/src/timetravel/SmarterContract.sol +++ b/src/timetravel/SmarterContract.sol @@ -9,6 +9,8 @@ import "../timetravel/CallBreaker.sol"; contract SmarterContract { CallBreaker public callbreaker; + mapping(address => bool) public auditedContracts; + mapping(address => bool) public ofacCensoredAddresses; /// @dev Selector 0xab63c583 error FutureCallExpected(); @@ -26,6 +28,12 @@ contract SmarterContract { /// @dev Selector 0xd1cb360d error IllegalBackrun(); + /// @dev Selector 0xed32fe28 + error Unaudited(); + + /// @dev Selector 0xc19f17a9 + error NotApproved(); + /// @dev Constructs a new SmarterContract instance /// @param _callbreaker The address of the CallBreaker contract constructor(address _callbreaker) { @@ -53,6 +61,47 @@ contract SmarterContract { _; } + modifier onlyAudited(address _address) { + auditedBlocker(_address); + _; + } + + modifier onlyOFACApproved(address _address) { + OFAC_censoredBlocker(_address); + _; + } + + /// @notice Prevents execution by un-audited contracts + /// @dev This function checks whether the provided address is in the list of audited contracts + /// @param _address The address to check + function auditedBlocker(address _address) public view { + if (!auditedContracts[_address]) { + revert Unaudited(); + } + } + + /// @notice Prevents calls to all addresses in the list + /// @dev This function checks whether the provided address is in the list of OFAC censored addresses + function OFAC_censoredBlocker(address _address) public view { + if (ofacCensoredAddresses[_address]) { + revert NotApproved(); + } + } + + /// @notice Sets a contract as OFAC blocked + /// @dev This function adds the provided address to the list of OFAC censored addresses + /// @param _address The address to be added to the list of OFAC censored addresses + function setOFACBlocked(address _address) public { + ofacCensoredAddresses[_address] = true; + } + + /// @notice Sets a contract as audited + /// @dev This function adds the provided address to the list of audited contracts + /// @param _address The address to be added to the list of audited contracts + function setAuditedContract(address _address) public { + auditedContracts[_address] = true; + } + /// @notice Returns the call index, callobj, and returnobj of the currently executing call /// @dev This function allows for time travel by returning the returnobj of the currently executing call /// @return A pair consisting of the CallObject and ReturnObject of the currently executing call @@ -90,7 +139,7 @@ contract SmarterContract { /// @notice Ensures that there is a future call to the specified callobject after the current call /// @dev This iterates over all call indices and ensures there's one after the current call. /// Adding a hintdex makes this cheaper. - /// @param callObj The callobject to check for future calls + /// @param callObj The callobject to check for. This callObject should strictly be a future call function assertFutureCallTo(CallObject memory callObj) public view { uint256[] memory cinds = callbreaker.getCallIndex(callObj); uint256 currentlyExecuting = callbreaker.getCurrentlyExecuting(); @@ -103,7 +152,7 @@ contract SmarterContract { } /// @notice Ensures that there is a future call to the specified callobject after the current call - /// @param callObj The callobject to check for future calls + /// @param callObj The callobject to check for. This callObject should strictly be a future call /// @param hintdex The hint index to start checking for future calls /// @custom:reverts FutureCallExpected() Hintdexes should always be in the future of the current executing call /// @custom:reverts CallMismatch() The callobject at the hintdex should match the specified callObject diff --git a/src/tips/Tips.sol b/src/tips/Tips.sol deleted file mode 100644 index db3730b..0000000 --- a/src/tips/Tips.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity >=0.6.2 <0.9.0; - -import "../timetravel/CallBreaker.sol"; - -contract Tips { - event Tip(address indexed from, address indexed to, uint256 amount); - - /// @dev Error thrown when receiving empty calldata - /// @dev Selector 0xc047a184 - error EmptyCalldata(); - - CallBreaker public callbreaker; - - constructor(address _callbreaker) { - callbreaker = CallBreaker(payable(_callbreaker)); - } - - /// @dev Tips should be transferred from each LaminatorProxy to the solver via msg.value - receive() external payable { - bytes32 tipAddrKey = keccak256(abi.encodePacked("tipYourBartender")); - bytes memory tipAddrBytes = callbreaker.fetchFromAssociatedDataStore(tipAddrKey); - address tipAddr = abi.decode(tipAddrBytes, (address)); - emit Tip(msg.sender, tipAddr, msg.value); - payable(tipAddr).transfer(msg.value); - } -} diff --git a/test/CallBreaker.t.sol b/test/CallBreaker.t.sol index 7a97389..98c7710 100644 --- a/test/CallBreaker.t.sol +++ b/test/CallBreaker.t.sol @@ -42,10 +42,6 @@ contract CallBreakerHarness is CallBreaker { function expectCallAtHarness(CallObject memory callObj, uint256 index) public view { _expectCallAt(callObj, index); } - - function getReturnHarness(uint256 index) public view returns (ReturnObject memory) { - return _getReturn(index); - } } contract CallBreakerTest is Test { diff --git a/test/GasSnapshot.t.sol b/test/GasSnapshot.t.sol index ef75c8c..ff3679f 100644 --- a/test/GasSnapshot.t.sol +++ b/test/GasSnapshot.t.sol @@ -7,6 +7,7 @@ import {LaminatorHarness} from "./Laminator.t.sol"; contract GasSnapshot is Test { LaminatorHarness laminator; + // TODO: Finish gas snapshots function setUp() public { laminator = new LaminatorHarness(); } diff --git a/test/Laminator.t.sol b/test/Laminator.t.sol index 3eb9091..d485f3f 100644 --- a/test/Laminator.t.sol +++ b/test/Laminator.t.sol @@ -58,7 +58,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val1) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val1), + delegate: false }); bytes memory cData = abi.encode(callObj1); vm.expectEmit(true, true, true, true); @@ -74,7 +75,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val2) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val2), + delegate: false }); cData = abi.encode(callObj2); vm.expectEmit(true, true, true, true); @@ -111,7 +113,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); uint256 sequenceNumber = laminator.pushToProxy(cData, 0); @@ -134,7 +137,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); uint256 sequenceNumber = laminator.pushToProxy(cData, 1); @@ -158,7 +162,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); uint256 sequenceNumber = laminator.pushToProxy(cData, 3); @@ -184,7 +189,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); vm.prank(randomFriendAddress); @@ -206,7 +212,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); vm.prank(address(laminator)); @@ -227,7 +234,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", val) + callvalue: abi.encodeWithSignature("emitArg(uint256)", val), + delegate: false }); bytes memory cData = abi.encode(callObj); uint256 sequenceNumber = laminator.pushToProxy(cData, 0); @@ -254,7 +262,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("reverter()") + callvalue: abi.encodeWithSignature("reverter()"), + delegate: false }); bytes memory cData = abi.encode(callObj); uint256 sequenceNumber = laminator.pushToProxy(cData, 1); @@ -277,7 +286,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", 42) + callvalue: abi.encodeWithSignature("emitArg(uint256)", 42), + delegate: false }); bytes memory cData = abi.encode(callObj); @@ -298,7 +308,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", 42) + callvalue: abi.encodeWithSignature("emitArg(uint256)", 42), + delegate: false }); bytes memory cData = abi.encode(callObjs); @@ -320,7 +331,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", 42) + callvalue: abi.encodeWithSignature("emitArg(uint256)", 42), + delegate: false }); bytes memory cData = abi.encode(callObj); @@ -358,7 +370,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", 42) + callvalue: abi.encodeWithSignature("emitArg(uint256)", 42), + delegate: false }); bytes memory cData = abi.encode(callObj); @@ -378,7 +391,8 @@ contract LaminatorTest is Test { amount: 0, addr: address(dummy), gas: gasleft(), - callvalue: abi.encodeWithSignature("emitArg(uint256)", 42) + callvalue: abi.encodeWithSignature("emitArg(uint256)", 42), + delegate: false }); bytes memory cData = abi.encode(callObj); diff --git a/test/NoopTurnerTest.t.sol b/test/NoopTurnerTest.t.sol index a3b44b3..2fc533c 100644 --- a/test/NoopTurnerTest.t.sol +++ b/test/NoopTurnerTest.t.sol @@ -27,7 +27,8 @@ contract NoopTurnerTest is Test { amount: 0, addr: address(noopturner), gas: 1000000, - callvalue: abi.encodeWithSignature("const_loop(uint16)", uint16(42)) + callvalue: abi.encodeWithSignature("const_loop()"), + delegate: false }); ReturnObject[] memory returnObjs = new ReturnObject[](1); diff --git a/test/PnP.t.sol b/test/PnP.t.sol new file mode 100644 index 0000000..5593570 --- /dev/null +++ b/test/PnP.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Test.sol"; +import "../src/timetravel/CallBreaker.sol"; +import "../test/examples/PnP.sol"; +import "../test/solve-lib/PnPExample.sol"; + +contract PnPTest is Test, PnPExampleLib { + address deployer; + address pusher; + address filler; + + function setUp() public { + deployer = address(100); + pusher = address(200); + filler = address(300); + + // give the pusher some eth + vm.deal(pusher, 100 ether); + + // start deployer land + vm.startPrank(deployer); + deployerLand(pusher); + vm.stopPrank(); + + // Label operations in the run function. + vm.label(pusher, "pusher"); + vm.label(address(this), "deployer"); + vm.label(filler, "filler"); + } + + function testPnP() external { + uint256 laminatorSequenceNumber; + + vm.startPrank(pusher); + laminatorSequenceNumber = userLand(); + vm.stopPrank(); + + // go forward in time + vm.roll(block.number + 1); + + vm.startPrank(filler); + solverLand(laminatorSequenceNumber, filler); + vm.stopPrank(); + + assertFalse(callbreaker.isPortalOpen()); + + (bool init, bool exec,) = LaminatedProxy(pusherLaminated).viewDeferredCall(laminatorSequenceNumber); + + assertTrue(init); + assertTrue(exec); + } +} diff --git a/test/Whitelist.t.sol b/test/Whitelist.t.sol new file mode 100644 index 0000000..b1f1656 --- /dev/null +++ b/test/Whitelist.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; + +import "./solve-lib/Whitelist.sol"; + +import "../src/lamination/Laminator.sol"; +import "../src/timetravel/CallBreaker.sol"; + +contract WhiteListedTest is Test, Whitelist { + address deployer; + address pusher; + address filler; + + function setUp() external { + deployer = address(100); + pusher = address(200); + filler = address(300); + + // give the pusher some eth + vm.deal(pusher, 100 ether); + + // start deployer calls + vm.startPrank(deployer); + deployerLand(pusher); + vm.stopPrank(); + + // Label operations in the run function. + vm.label(pusher, "pusher"); + vm.label(address(this), "deployer"); + vm.label(filler, "filler"); + } + + function testWhitelist() external { + uint256 laminatorSequenceNumber; + + vm.startPrank(pusher); + laminatorSequenceNumber = userLandWhitelist(); + vm.stopPrank(); + + // go forward in time + vm.roll(block.number + 1); + + vm.startPrank(filler); + solverLandWhitelist(laminatorSequenceNumber, filler); + vm.stopPrank(); + + assertFalse(callbreaker.isPortalOpen()); + + // Should be cleared so init should be false (testFail format is for compliance with Kontrol framework) + (bool init, bool exec,) = LaminatedProxy(pusherLaminated).viewDeferredCall(laminatorSequenceNumber); + + assertTrue(init); + assertTrue(exec); + } + + //' This is a test that should fail -- @TODO: Assert revert with message custom error c19f17a9: CallFailed() + function testFail_BlackList() external { + uint256 laminatorSequenceNumber; + + vm.startPrank(pusher); + laminatorSequenceNumber = userLandBlackList(); + vm.stopPrank(); + + // go forward in time + vm.roll(block.number + 1); + + vm.startPrank(filler); + solverLandBlackList(laminatorSequenceNumber, filler); + vm.stopPrank(); + + assertFalse(callbreaker.isPortalOpen()); + + // Should be cleared so init should be false (testFail format is for compliance with Kontrol framework) + (bool init, bool exec,) = LaminatedProxy(pusherLaminated).viewDeferredCall(laminatorSequenceNumber); + + assertTrue(init); + assertTrue(exec); + } +} diff --git a/test/examples/Caller.sol b/test/examples/Caller.sol new file mode 100644 index 0000000..21d73aa --- /dev/null +++ b/test/examples/Caller.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNKNOWN + +pragma solidity >=0.6.2 <0.9.0; + +import "../../src/timetravel/CallBreaker.sol"; +import "../../src/timetravel/SmarterContract.sol"; + +contract Caller is SmarterContract { + address private _callbreakerAddress; + + constructor(address callBreaker, address ofacBlocked, address audited) SmarterContract(callBreaker) { + _callbreakerAddress = callBreaker; + // Sets the following sample addresses to OFAC blocked and audited to proceed with tests + ofacCensoredAddresses[ofacBlocked] = true; + auditedContracts[audited] = true; + } + + function callWhitelisted(address target, bytes memory callData) public payable onlyAudited(target) { + (bool success, bytes memory returnData) = target.call{value: msg.value}(callData); + if (!success) { + revert(string(returnData)); + } + } + + function callAnyButBlacklisted(address target, bytes memory callData) public payable onlyOFACApproved(target) { + (bool success, bytes memory returnData) = target.call{value: msg.value}(callData); + if (!success) { + revert(string(returnData)); + } + } +} diff --git a/test/examples/FlashPill.sol b/test/examples/FlashPill.sol index 35fbb55..4bc0b8d 100644 --- a/test/examples/FlashPill.sol +++ b/test/examples/FlashPill.sol @@ -82,7 +82,8 @@ contract FlashPill is IERC20 { amount: 0, addr: address(this), gas: 1000000, - callvalue: abi.encodeWithSignature("moneyWasReturnedCheck()") + callvalue: abi.encodeWithSignature("moneyWasReturnedCheck()"), + delegate: false }); (bool success,) = _callbreakerAddress.call(abi.encode(callObjs)); diff --git a/test/examples/LimitOrder.sol b/test/examples/LimitOrder.sol index 3df0ab5..3715561 100644 --- a/test/examples/LimitOrder.sol +++ b/test/examples/LimitOrder.sol @@ -64,7 +64,8 @@ contract LimitOrder { amount: 0, addr: address(this), gas: 1000000, - callvalue: abi.encodeWithSignature("checkBalance()") + callvalue: abi.encodeWithSignature("checkBalance()"), + delegate: false }); emit LogCallObj(callObj); diff --git a/test/examples/NoopTurner.sol b/test/examples/NoopTurner.sol index 9075b48..a03ccf2 100644 --- a/test/examples/NoopTurner.sol +++ b/test/examples/NoopTurner.sol @@ -3,20 +3,25 @@ pragma solidity >=0.6.2 <0.9.0; import "../../src/timetravel/CallBreaker.sol"; +import "../../src/timetravel/SmarterContract.sol"; +import "../../src/TimeTypes.sol"; -contract NoopTurner { +contract NoopTurner is SmarterContract { address private _callbreakerAddress; - constructor(address callbreakerLocation) { - _callbreakerAddress = callbreakerLocation; + constructor(address callbreakerAddress) SmarterContract(callbreakerAddress) { + _callbreakerAddress = callbreakerAddress; } - // dumb noop function without callbreaker + // noop function without callbreaker function vanilla(uint16 /* _input */ ) public pure returns (uint16) { return 52; } - function const_loop(uint16 input) external pure returns (uint16) { - return vanilla(input); + function const_loop() external view returns (uint16) { + // this one just returns whatever it gets at solvetime via. associatedDataStore + uint256 callIndex = CallBreaker(payable(_callbreakerAddress)).executingCallIndex(); + + return abi.decode(CallBreaker(payable(_callbreakerAddress)).getReturnValue(callIndex), (uint16)); } } diff --git a/test/examples/PnP.sol b/test/examples/PnP.sol index 946f8e6..2e0f897 100644 --- a/test/examples/PnP.sol +++ b/test/examples/PnP.sol @@ -9,13 +9,12 @@ contract PnP { address[] private addrlist; - // @TODO: this example will soon be refactored to use the latest associatedValue semantics constructor(address callbreakerLocation, uint256 input) { _callbreakerAddress = callbreakerLocation; // populate addrlist with a hash chain to look "random" - addrlist.push(address(uint160(uint256(keccak256(abi.encodePacked(input)))))); + addrlist.push(hash(input)); for (uint256 i = 1; i < 100000; i++) { - addrlist.push(address(uint160(uint256(keccak256(abi.encodePacked(addrlist[i - 1])))))); + addrlist.push(hash(addrlist[i - 1])); } } @@ -26,7 +25,7 @@ contract PnP { return false; } - // obviously not np but linear rather than constant time + // not np, but linearly searches a huge array rather than constant time function np(address input) external view returns (uint256, bool) { uint256 index = 0; for (uint256 i = 0; i < 100000; i++) { @@ -37,25 +36,24 @@ contract PnP { return (0, false); } - function callBreakerNp(address input) external returns (uint256 index) { - CallObject memory callObj = CallObject({ - amount: 0, - addr: address(this), - gas: 1000000, - callvalue: abi.encodeWithSignature("const_loop(uint16)", input) - }); + // uses an index hint to find the correct item in the array + function callBreakerNp(address input) external view returns (uint256 index) { + // Get a hint index (hintdex) from the solver, likely computed off-chain, where the correct object is. + bytes32 hintKey = keccak256(abi.encodePacked("hintdex")); + bytes memory hintBytes = CallBreaker(payable(_callbreakerAddress)).fetchFromAssociatedDataStore(hintKey); - // call, hit the fallback. - (bool success, bytes memory returnvalue) = _callbreakerAddress.call(abi.encode(callObj)); - - if (!success) { - revert("turner CallFailed"); - } - - uint256 returnedvalue = abi.decode(returnvalue, (uint256)); + uint256 returnedvalue = abi.decode(hintBytes, (uint256)); require(this.p(input, returnedvalue), "callBreakerNp, hint wrong"); return returnedvalue; } + + function hash(uint256 input) public pure returns (address) { + return address(uint160(uint256(keccak256(abi.encodePacked(input))))); + } + + function hash(address input) public pure returns (address) { + return address(uint160(uint256(keccak256(abi.encodePacked(input))))); + } } diff --git a/test/examples/SelfCheckout.sol b/test/examples/SelfCheckout.sol index e924cf8..cada7a6 100644 --- a/test/examples/SelfCheckout.sol +++ b/test/examples/SelfCheckout.sol @@ -75,7 +75,8 @@ contract SelfCheckout is SmarterContract { amount: 0, addr: address(this), gas: 1000000, - callvalue: abi.encodeWithSignature("checkBalance()") + callvalue: abi.encodeWithSignature("checkBalance()"), + delegate: false }); emit LogCallObj(callObj); assertFutureCallTo(callObj, 2); diff --git a/test/solve-lib/CronTwo.sol b/test/solve-lib/CronTwo.sol index f7a70ad..f4ac160 100644 --- a/test/solve-lib/CronTwo.sol +++ b/test/solve-lib/CronTwo.sol @@ -6,7 +6,6 @@ import "forge-std/Vm.sol"; import "../../src/lamination/Laminator.sol"; import "../../src/timetravel/CallBreaker.sol"; import "../../test/examples/CronTwoCounter.sol"; -import "../../src/tips/Tips.sol"; import "../../src/timetravel/SmarterContract.sol"; // for the next year, every day: @@ -20,7 +19,6 @@ contract CronTwoLib { Laminator public laminator; CronTwoCounter public counter; CallBreaker public callbreaker; - Tips public tips; uint32 _blocksInADay = 7150; uint256 _tipWei = 33; @@ -30,7 +28,6 @@ contract CronTwoLib { callbreaker = new CallBreaker(); counter = new CronTwoCounter(address(callbreaker)); pusherLaminated = payable(laminator.computeProxyAddress(pusher)); - tips = new Tips(address(callbreaker)); } function userLand() public returns (uint256) { @@ -43,30 +40,35 @@ contract CronTwoLib { amount: 0, addr: address(counter), gas: 10000000, - callvalue: abi.encodeWithSignature("increment()") + callvalue: abi.encodeWithSignature("increment()"), + delegate: false }); - pusherCallObjs[1] = CallObject({amount: _tipWei, addr: address(tips), gas: 10000000, callvalue: ""}); + pusherCallObjs[1] = + CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: "", delegate: false}); CallObject memory callObjectContinueFunctionPointer = CallObject({ amount: 0, addr: address(counter), gas: 10000000, - callvalue: abi.encodeWithSignature("shouldContinue()") + callvalue: abi.encodeWithSignature("shouldContinue()"), + delegate: false }); bytes memory callObjectContinueFnPtr = abi.encode(callObjectContinueFunctionPointer); pusherCallObjs[2] = CallObject({ amount: 0, addr: pusherLaminated, gas: 10000000, - callvalue: abi.encodeWithSignature("copyCurrentJob(uint256,bytes)", _blocksInADay, callObjectContinueFnPtr) + callvalue: abi.encodeWithSignature("copyCurrentJob(uint256,bytes)", _blocksInADay, callObjectContinueFnPtr), + delegate: false }); pusherCallObjs[3] = CallObject({ amount: 0, addr: address(counter), gas: 10000000, - callvalue: abi.encodeWithSignature("frontrunBlocker()") + callvalue: abi.encodeWithSignature("frontrunBlocker()"), + delegate: false }); return laminator.pushToProxy(abi.encode(pusherCallObjs), 1); } @@ -79,7 +81,8 @@ contract CronTwoLib { amount: 0, addr: pusherLaminated, gas: 10000000, - callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber) + callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber), + delegate: false }); ReturnObject[] memory returnObjsFromPull = new ReturnObject[](4); diff --git a/test/solve-lib/PnPExample.sol b/test/solve-lib/PnPExample.sol new file mode 100644 index 0000000..5693ad5 --- /dev/null +++ b/test/solve-lib/PnPExample.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Vm.sol"; + +import "../../src/lamination/Laminator.sol"; +import "../../src/timetravel/CallBreaker.sol"; +import "../../test/examples/PnP.sol"; +import "../../src/timetravel/SmarterContract.sol"; + +contract PnPExampleLib { + address payable public pusherLaminated; + PnP public pnp; + Laminator public laminator; + CallBreaker public callbreaker; + uint256 _tipWei = 33; + uint256 hashChainInitConst = 1; + + function deployerLand(address pusher) public { + // Initializing contracts + laminator = new Laminator(); + callbreaker = new CallBreaker(); + pnp = new PnP(address(callbreaker), hashChainInitConst); + pusherLaminated = payable(laminator.computeProxyAddress(pusher)); + } + + function userLand() public returns (uint256) { + // send proxy some eth + pusherLaminated.transfer(1 ether); + + // 5th member of the hash-chain + address fifthInList = PnP(address(pnp)).hash( + PnP(address(pnp)).hash( + PnP(address(pnp)).hash(PnP(address(pnp)).hash(PnP(address(pnp)).hash(hashChainInitConst))) + ) + ); + + // Userland operations + CallObject[] memory pusherCallObjs = new CallObject[](2); + pusherCallObjs[0] = CallObject({ + amount: 0, + addr: address(pnp), + gas: 1000000, + callvalue: abi.encodeWithSignature("callBreakerNp(address)", fifthInList), + delegate: false + }); + + pusherCallObjs[1] = + CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: "", delegate: false}); + + return laminator.pushToProxy(abi.encode(pusherCallObjs), 1); + } + + function solverLand(uint256 laminatorSequenceNumber, address filler) public { + CallObject[] memory callObjs = new CallObject[](1); + ReturnObject[] memory returnObjs = new ReturnObject[](1); + + callObjs[0] = CallObject({ + amount: 0, + addr: pusherLaminated, + gas: 10000000, + callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber), + delegate: false + }); + + ReturnObject[] memory returnObjsFromPull = new ReturnObject[](2); + returnObjsFromPull[0] = ReturnObject({returnvalue: abi.encode(4)}); + returnObjsFromPull[1] = ReturnObject({returnvalue: ""}); + + returnObjs[0] = ReturnObject({returnvalue: abi.encode(abi.encode(returnObjsFromPull))}); + + bytes32[] memory keys = new bytes32[](3); + keys[0] = keccak256(abi.encodePacked("tipYourBartender")); + keys[1] = keccak256(abi.encodePacked("pullIndex")); + keys[2] = keccak256(abi.encodePacked("hintdex")); + bytes[] memory values = new bytes[](3); + values[0] = abi.encode(filler); + values[1] = abi.encode(laminatorSequenceNumber); + values[2] = abi.encode(4); + bytes memory encodedData = abi.encode(keys, values); + + bytes32[] memory hintdicesKeys = new bytes32[](1); + hintdicesKeys[0] = keccak256(abi.encode(callObjs[0])); + uint256[] memory hintindicesVals = new uint256[](1); + hintindicesVals[0] = 0; + bytes memory hintdices = abi.encode(hintdicesKeys, hintindicesVals); + callbreaker.verify(abi.encode(callObjs), abi.encode(returnObjs), encodedData, hintdices); + } +} diff --git a/test/solve-lib/Whitelist.sol b/test/solve-lib/Whitelist.sol new file mode 100644 index 0000000..c96a4a1 --- /dev/null +++ b/test/solve-lib/Whitelist.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Vm.sol"; + +import "../../src/lamination/Laminator.sol"; +import "../../src/timetravel/CallBreaker.sol"; +import "../../test/examples/Caller.sol"; +import "../../src/timetravel/SmarterContract.sol"; +import "../../test/examples/NoopTurner.sol"; + +contract Whitelist { + address payable public pusherLaminated; + NoopTurner public noopturner_ofacBlocked; + NoopTurner public noopturner_audited; + Laminator public laminator; + Caller public caller; + CallBreaker public callbreaker; + uint256 _tipWei = 33; + + function deployerLand(address pusher) public { + // Initializing contracts + laminator = new Laminator(); + callbreaker = new CallBreaker(); + noopturner_ofacBlocked = new NoopTurner(address(callbreaker)); + noopturner_audited = new NoopTurner(address(callbreaker)); + caller = new Caller(address(callbreaker), address(noopturner_ofacBlocked), address(noopturner_audited)); + pusherLaminated = payable(laminator.computeProxyAddress(pusher)); + } + + function userLandWhitelist() public returns (uint256) { + // send proxy some eth + pusherLaminated.transfer(1 ether); + + // Userland operations + CallObject[] memory pusherCallObjs = new CallObject[](2); + pusherCallObjs[0] = CallObject({ + amount: 0, + addr: address(caller), + gas: 1000000, + callvalue: abi.encodeWithSignature( + "callWhitelisted(address,bytes)", + address(noopturner_audited), + abi.encodeWithSignature("vanilla(uint16)", uint16(42)) + ), + delegate: false + }); + + pusherCallObjs[1] = + CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: "", delegate: false}); + + return laminator.pushToProxy(abi.encode(pusherCallObjs), 1); + } + + function solverLandWhitelist(uint256 laminatorSequenceNumber, address filler) public { + CallObject[] memory callObjs = new CallObject[](1); + ReturnObject[] memory returnObjs = new ReturnObject[](1); + + callObjs[0] = CallObject({ + amount: 0, + addr: pusherLaminated, + gas: 10000000, + callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber), + delegate: false + }); + + ReturnObject[] memory returnObjsFromPull = new ReturnObject[](2); + returnObjsFromPull[0] = ReturnObject({returnvalue: ""}); + returnObjsFromPull[1] = ReturnObject({returnvalue: ""}); + + returnObjs[0] = ReturnObject({returnvalue: abi.encode(abi.encode(returnObjsFromPull))}); + + bytes32[] memory keys = new bytes32[](2); + keys[0] = keccak256(abi.encodePacked("tipYourBartender")); + keys[1] = keccak256(abi.encodePacked("pullIndex")); + bytes[] memory values = new bytes[](2); + values[0] = abi.encode(filler); + values[1] = abi.encode(laminatorSequenceNumber); + bytes memory encodedData = abi.encode(keys, values); + + bytes32[] memory hintdicesKeys = new bytes32[](1); + hintdicesKeys[0] = keccak256(abi.encode(callObjs[0])); + uint256[] memory hintindicesVals = new uint256[](1); + hintindicesVals[0] = 0; + bytes memory hintdices = abi.encode(hintdicesKeys, hintindicesVals); + callbreaker.verify(abi.encode(callObjs), abi.encode(returnObjs), encodedData, hintdices); + } + + function userLandBlackList() public returns (uint256) { + // send proxy some eth + pusherLaminated.transfer(1 ether); + + // Userland operations + CallObject[] memory pusherCallObjs = new CallObject[](2); + pusherCallObjs[0] = CallObject({ + amount: 0, + addr: address(caller), + gas: 1000000, + callvalue: abi.encodeWithSignature( + "callAnyButBlacklisted(address,bytes)", + address(noopturner_ofacBlocked), + abi.encodeWithSignature("vanilla(uint16)", uint16(42)) + ), + delegate: false + }); + + pusherCallObjs[1] = + CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: "", delegate: false}); + + return laminator.pushToProxy(abi.encode(pusherCallObjs), 1); + } + + function solverLandBlackList(uint256 laminatorSequenceNumber, address filler) public { + CallObject[] memory callObjs = new CallObject[](1); + ReturnObject[] memory returnObjs = new ReturnObject[](1); + + callObjs[0] = CallObject({ + amount: 0, + addr: pusherLaminated, + gas: 10000000, + callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber), + delegate: false + }); + + ReturnObject[] memory returnObjsFromPull = new ReturnObject[](2); + returnObjsFromPull[0] = ReturnObject({returnvalue: ""}); + returnObjsFromPull[1] = ReturnObject({returnvalue: ""}); + + returnObjs[0] = ReturnObject({returnvalue: abi.encode(abi.encode(returnObjsFromPull))}); + + bytes32[] memory keys = new bytes32[](2); + keys[0] = keccak256(abi.encodePacked("tipYourBartender")); + keys[1] = keccak256(abi.encodePacked("pullIndex")); + bytes[] memory values = new bytes[](2); + values[0] = abi.encode(filler); + values[1] = abi.encode(laminatorSequenceNumber); + bytes memory encodedData = abi.encode(keys, values); + + bytes32[] memory hintdicesKeys = new bytes32[](1); + hintdicesKeys[0] = keccak256(abi.encode(callObjs[0])); + uint256[] memory hintindicesVals = new uint256[](1); + hintindicesVals[0] = 0; + bytes memory hintdices = abi.encode(hintdicesKeys, hintindicesVals); + callbreaker.verify(abi.encode(callObjs), abi.encode(returnObjs), encodedData, hintdices); + } + + //@TODO: add unaudited and normal cases +} diff --git a/test/solve-lib/WorkedExample.sol b/test/solve-lib/WorkedExample.sol index 0decfe9..fc9d03d 100644 --- a/test/solve-lib/WorkedExample.sol +++ b/test/solve-lib/WorkedExample.sol @@ -7,7 +7,6 @@ import "../../src/lamination/Laminator.sol"; import "../../src/timetravel/CallBreaker.sol"; import "../../test/examples/SelfCheckout.sol"; import "../../test/examples/MyErc20.sol"; -import "../../src/tips/Tips.sol"; contract WorkedExampleLib { CallBreaker public callbreaker; @@ -16,7 +15,6 @@ contract WorkedExampleLib { Laminator public laminator; MyErc20 public erc20a; MyErc20 public erc20b; - Tips public tips; uint256 _tipWei = 100000000000000000; @@ -27,7 +25,6 @@ contract WorkedExampleLib { erc20a = new MyErc20("A", "A"); erc20b = new MyErc20("B", "B"); - tips = new Tips(address(callbreaker)); // give the pusher 10 erc20a erc20a.mint(pusher, 10); @@ -47,19 +44,22 @@ contract WorkedExampleLib { pusherLaminated.transfer(1 ether); erc20a.transfer(pusherLaminated, 10); CallObject[] memory pusherCallObjs = new CallObject[](3); - pusherCallObjs[0] = CallObject({amount: _tipWei, addr: address(tips), gas: 10000000, callvalue: ""}); + pusherCallObjs[0] = + CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: "", delegate: false}); pusherCallObjs[1] = CallObject({ amount: 0, addr: address(erc20a), gas: 1000000, - callvalue: abi.encodeWithSignature("approve(address,uint256)", address(selfcheckout), 10) + callvalue: abi.encodeWithSignature("approve(address,uint256)", address(selfcheckout), 10), + delegate: false }); pusherCallObjs[2] = CallObject({ amount: 0, addr: address(selfcheckout), gas: 1000000, - callvalue: abi.encodeWithSignature("takeSomeAtokenFromOwner(uint256)", 10) + callvalue: abi.encodeWithSignature("takeSomeAtokenFromOwner(uint256)", 10), + delegate: false }); laminator.pushToProxy(abi.encode(pusherCallObjs), 1); @@ -76,7 +76,8 @@ contract WorkedExampleLib { amount: 0, addr: pusherLaminated, gas: 1000000, - callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber) + callvalue: abi.encodeWithSignature("pull(uint256)", laminatorSequenceNumber), + delegate: false }); // should return a list of the return value of approve + takesomeatokenfrompusher in a list of returnobjects, abi packed, then stuck into another returnobject. ReturnObject[] memory returnObjsFromPull = new ReturnObject[](3); @@ -91,7 +92,8 @@ contract WorkedExampleLib { amount: 0, addr: address(selfcheckout), gas: 1000000, - callvalue: abi.encodeWithSignature("giveSomeBtokenToOwner(uint256)", x) + callvalue: abi.encodeWithSignature("giveSomeBtokenToOwner(uint256)", x), + delegate: false }); // return object is still nothing returnObjs[1] = ReturnObject({returnvalue: ""}); @@ -101,7 +103,8 @@ contract WorkedExampleLib { amount: 0, addr: address(selfcheckout), gas: 1000000, - callvalue: abi.encodeWithSignature("checkBalance()") + callvalue: abi.encodeWithSignature("checkBalance()"), + delegate: false }); // log what this callobject looks like // return object is still nothing