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