Skip to main content

LaminatedProxy

The LaminatedProxy contract acts as a personalized transaction manager for each user (eg developer, dapp, protocol). It serves as a mempool where users can queue their transaction requests with specific conditions. The contract manages the execution order, timing, and other parameters of the transactions.

Customizable Transaction Execution: Users can set specific conditions for their transactions within the LaminatedProxy, such as preventing frontruns, specifying delays, or setting order dependencies.

For example, suppose you want to have a simple scheduler that shouldn't be frontrun. You would first create CallObjects for the job, and then push it into the LaminatedProxy.

    function userLand() public returns (uint256) {
// send proxy some eth
pusherLaminated.transfer(1 ether);

// Cron job to increment a counter
CallObject[] memory pusherCallObjs = new CallObject[](4);
pusherCallObjs[0] = CallObject({
amount: 0,
addr: address(counter),
gas: 10000000,
callvalue: abi.encodeWithSignature("increment()")
});

// Tip your solver!
pusherCallObjs[1] = CallObject({amount: _tipWei, addr: address(callbreaker), gas: 10000000, callvalue: ""});

// Confirm continuation of the cron job
CallObject memory callObjectContinueFunctionPointer = CallObject({
amount: 0,
addr: address(counter),
gas: 10000000,
callvalue: abi.encodeWithSignature("shouldContinue()")
});

bytes memory callObjectContinueFnPtr = abi.encode(callObjectContinueFunctionPointer);
// Copy the existing job to execute again as cron action
pusherCallObjs[2] = CallObject({
amount: 0,
addr: pusherLaminated,
gas: 10000000,
callvalue: abi.encodeWithSignature("copyCurrentJob(uint256,bytes)", _blocksInADay, callObjectContinueFnPtr)
});

// Prevent frontruns
pusherCallObjs[3] = CallObject({
amount: 0,
addr: address(counter),
gas: 10000000,
callvalue: abi.encodeWithSignature("frontrunBlocker()")
});
return laminator.pushToProxy(abi.encode(pusherCallObjs), 1);
}

The Laminator contains a function, push, that is designed to schedule a deferred function call within a smart contract environment. As referenced in the previous sections, it accepts an encoded array of CallObjects and a delay parameter specifying the number of blocks to wait before execution.

The function is restricted to be callable only by specific contracts (Laminator or LaminatedProxy) and supports re-entrant calls to facilitate schedulers with tail recursion. Upon invocation, it decodes the input, initializes a CallObjectHolder, assigns a sequence number, and stores the call for delayed execution.

/// @param input The encoded CallObject containing information about the function call to defer.
/// @param delay The number of blocks to delay before the function call can be executed.
/// Use 0 for no delay.
/// @return callSequenceNumber The sequence number assigned to this deferred call.
function push(bytes memory input, uint256 delay) public onlyLaminatorOrProxy returns (uint256 callSequenceNumber) {
CallObjectHolder memory holder;
holder.callObjs = abi.decode(input, (CallObject[]));
callSequenceNumber = count();
holder.initialized = true;
holder.executed = false;
holder.firstCallableBlock = block.number + delay;
_deferredCalls[callSequenceNumber].store(holder);

emit CallableBlock(block.number + delay, block.number);
emit CallPushed(holder.callObjs, callSequenceNumber);
_incrementSequenceNumber();
}

The pull function is crucial for executing deferred calls at the appropriate time, ensuring that all conditions and state changes are logged and managed correctly. It sequentially executes all the calls in the CallObjectHolder and emits events to log the executed calls and the sequence number.

/// @param seqNumber The sequence number of the deferred call to be executed.
/// @return returnValue The return value of the executed deferred call.
function pull(uint256 seqNumber) external nonReentrant returns (bytes memory returnValue) {
CallObjectHolderStorage storage cohStorage = _deferredCalls[seqNumber];
_checkPrePush(cohStorage);

cohStorage.executed = true;
_setCurrentlyExecutingSeqNum(seqNumber);
_setCurrentlyExecutingCallIndex(0);
_setExecuting();
CallObjectHolder memory coh = cohStorage.load();
emit CallableBlock(coh.firstCallableBlock, block.number);

returnValue = _executeAll(coh.callObjs);
emit CallPulled(coh.callObjs, seqNumber);
_setFree();
}

There are other functions that assist with the execution of the transactions:

copyCurrentJob: This function allows the proxy to copy the current job and schedule it to run at a later time. It is useful for creating schedulers that should run at a specific interval, such as every day or every week.

function copyCurrentJob(uint256 delay, bytes calldata shouldCopy) external onlyWhileExecuting returns (uint256) {
if (msg.sender != address(this)) {
revert NotProxy();
}
return _copyJob(executingSequenceNumber(), delay, shouldCopy);
}

cancelAllPending: This function allows the proxy to cancel all pending jobs. It is useful for cancelling all pending jobs if the proxy is not working as expected.

function cancelAllPending() external onlyOwner {
for (uint256 i = 0; i < executingSequenceNumber(); i++) {
if (_deferredCalls[i].executed == false) {
_deferredCalls[i].executed = true;
}
}
}

ETH-Receiving Contract: Each laminatedProxy serves almost as a smart contract wallet, where the laminatedProxy can receive ETH for operations like push / pull to be executed with the balance.

Interface with CallBreaker: The LaminatedProxy interfaces with the CallBreaker for the actual execution of the transactions. It relays the queued transactions along with their conditions to the CallBreaker, which then executes them accordingly.