Skip to content

Commit

Permalink
Merge pull request #97 from smart-transaction/feat/eliminate-use-of-b…
Browse files Browse the repository at this point in the history
…ytes-in-function-interfaces

Feat/eliminate use of bytes in function interfaces
  • Loading branch information
TokenTitan authored Dec 16, 2024
2 parents c7510ef + 93861ec commit e869ae7
Show file tree
Hide file tree
Showing 20 changed files with 181 additions and 287 deletions.
2 changes: 1 addition & 1 deletion src/interfaces/IFlashLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity 0.8.26;

interface IFlashLoan {
function flashLoan(address receiver, uint256 amountA, uint256 amountB, bytes calldata data)
function flashLoan(address receiver, uint256 amountA, uint256 amountB, bytes memory data)
external
returns (bool);
}
4 changes: 2 additions & 2 deletions src/interfaces/ILaminator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ interface ILaminator {
/// @notice Calls the `push` function into the LaminatedProxy associated with the sender.
/// @dev Encodes the provided calldata and calls it into the `push` function of the proxy contract.
/// A new proxy will be created if one does not already exist for the sender.
/// @param cData The calldata to be pushed.
/// @param callObjs The calldatas to be pushed.
/// @param delay The delay for when the call can be executed.
/// @param selector code identifier for solvers to select relevant actions
/// @param dataValues to be used by solvers in serving the user objective
/// @return sequenceNumber The sequence number of the deferred function call.
function pushToProxy(bytes calldata cData, uint32 delay, bytes32 selector, SolverData[] memory dataValues)
function pushToProxy(CallObject[] memory callObjs, uint32 delay, bytes32 selector, SolverData[] memory dataValues)
external
returns (uint256 sequenceNumber);
}
15 changes: 7 additions & 8 deletions src/lamination/LaminatedProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,10 @@ contract LaminatedProxy is LaminatedStorage, ReentrancyGuard {
/// @notice Executes a function call immediately.
/// @dev Decodes the provided `input` into a CallObject and then calls `_execute`.
/// Can only be invoked by the owner of the contract.
/// @param input The encoded CallObject containing information about the function call to execute.
/// @param callObjs The CallObjects containing information about the function call to execute.
/// @return returnValue The return value from the executed function call.
function execute(bytes calldata input) external onlyOwner nonReentrant returns (bytes memory) {
CallObject[] memory callsToMake = abi.decode(input, (CallObject[]));
return _executeAll(callsToMake);
function execute(CallObject[] calldata callObjs) external onlyOwner nonReentrant returns (bytes memory) {
return _executeAll(callObjs);
}

/// @notice Copies the current job with a specified delay and condition.
Expand Down Expand Up @@ -215,17 +214,17 @@ contract LaminatedProxy is LaminatedStorage, ReentrancyGuard {
/// @dev Adds a new CallObject to the `deferredCalls` mapping and emits a CallPushed event.
/// The function can only be called by the Laminator or the LaminatedProxy contract itself.
/// It can also be called re-entrantly to enable the contract to do cronjobs with tail recursion.
/// @param input The encoded CallObject containing information about the function call to defer.
/// @param callObjs The CallObjects containing information about the function call to defer.
/// @param delay The number of blocks to delay before the function call can be executed.
/// @param data Additional data to be associated with the sequence of call objs
/// @return callSequenceNumber The sequence number assigned to this deferred call.
function push(bytes memory input, uint256 delay, SolverData[] memory data)
function push(CallObject[] memory callObjs, uint256 delay, SolverData[] memory data)
public
onlyLaminatorOrProxy
returns (uint256 callSequenceNumber)
{
CallObjectHolder memory holder;
holder.callObjs = abi.decode(input, (CallObject[]));
holder.callObjs = callObjs;
callSequenceNumber = _incrementSequenceNumber();
holder.initialized = true;
holder.executed = false;
Expand Down Expand Up @@ -288,7 +287,7 @@ contract LaminatedProxy is LaminatedStorage, ReentrancyGuard {
_checkInitialized(coh);
CallObjectHolder memory holder = coh.load();

return push(abi.encode(holder.callObjs), delay, holder.data);
return push(holder.callObjs, delay, holder.data);
}

/// @dev Safety checks before pushing calls to the LaminatedProxy
Expand Down
38 changes: 18 additions & 20 deletions src/lamination/Laminator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,41 +55,39 @@ contract Laminator is ILaminator {
return address(uint160(uint256(hash)));
}

/// @notice Gets the next sequence number of the LaminatedProxy associated with the sender.
/// @return sequenceNumber The sequence number of the next deferred function call.
function getNextSeqNumber() external view returns (uint256) {
address addr = computeProxyAddress(msg.sender);
uint32 size;
assembly {
size := extcodesize(addr)
}
if (size == 0) {
return 0;
} else {
return LaminatedProxy(payable(addr)).nextSequenceNumber();
}
}

/// @notice Calls the `push` function into the LaminatedProxy associated with the sender.
/// @dev Encodes the provided calldata and calls it into the `push` function of the proxy contract.
/// A new proxy will be created if one does not already exist for the sender.
/// @param cData The calldata to be pushed.
/// @param callObjs The calldatas to be pushed.
/// @param delay The delay for when the call can be executed.
/// @param selector code identifier for solvers to select relevant actions
/// @param dataValues to be used by solvers in serving the user objective
/// @return sequenceNumber The sequence number of the deferred function call.
function pushToProxy(bytes calldata cData, uint32 delay, bytes32 selector, SolverData[] memory dataValues)
function pushToProxy(CallObject[] memory callObjs, uint32 delay, bytes32 selector, SolverData[] memory dataValues)
external
returns (uint256 sequenceNumber)
{
LaminatedProxy proxy = LaminatedProxy(payable(_getOrCreateProxy(msg.sender)));
sequenceNumber = proxy.push(callObjs, delay, dataValues);

sequenceNumber = proxy.push(cData, delay, dataValues);

CallObject[] memory callObjs = abi.decode(cData, (CallObject[]));
emit ProxyPushed(address(proxy), callObjs, sequenceNumber, selector, dataValues);
}

/// @notice Gets the next sequence number of the LaminatedProxy associated with the sender.
/// @return sequenceNumber The sequence number of the next deferred function call.
function getNextSeqNumber() external view returns (uint256) {
address addr = computeProxyAddress(msg.sender);
uint32 size;
assembly {
size := extcodesize(addr)
}
if (size == 0) {
return 0;
} else {
return LaminatedProxy(payable(addr)).nextSequenceNumber();
}
}

/// @notice Gets the proxy address for the sender or creates a new one if it doesn't exist.
/// @dev Computes the proxy address for the sender using `computeProxyAddress`. If a proxy doesn't
/// already exist, it will be created using the `create2` Ethereum opcode.
Expand Down
95 changes: 39 additions & 56 deletions src/timetravel/CallBreaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,38 +58,36 @@ contract CallBreaker is CallBreakerStorage {

/// @notice executes and verifies that the given calls, when executed, gives the correct return values
/// @dev SECURITY NOTICE: This function is only callable when the portal is closed. It requires the caller to be an EOA.
/// @param callsBytes The bytes representing the calls to be verified
/// @param returnsBytes The bytes representing the returns to be verified against
/// @param associatedData Bytes representing associated data with the verify call, reserved for tipping the solver
/// @param callsBytes The calls to be executed
/// @param returnsBytes The returns to be compareed with return values from call obj execution
/// @param associatedData To be used in the execute and verify call, also reserved for tipping the solver
function executeAndVerify(
bytes calldata callsBytes,
bytes calldata returnsBytes,
bytes calldata associatedData,
bytes calldata hintdices
CallObject[] calldata callsBytes,
ReturnObject[] calldata returnsBytes,
AdditionalData[] calldata associatedData
) external payable onlyPortalClosed {
CallObject[] memory calls = _setupExecutionData(callsBytes, returnsBytes, associatedData, hintdices);
_executeAndVerifyCalls(calls);
CallObject[] memory calls = _setupExecutionData(callsBytes, returnsBytes, associatedData);
bytes memory data = abi.encode(calls);
_executeAndVerifyCalls(data);
}

/// @notice fetches flash loan before executing and verifying call objects who might use the loaned amount
/// @dev SECURITY NOTICE: This function is a temporary place holder for a nested call objects solution which is still under development
/// TODO: Remove and replace with a generic version of nested call objects
/// @param callsBytes The bytes representing the calls to be verified
/// @param returnsBytes The bytes representing the returns to be verified against
/// @param associatedData Bytes representing associated data with the verify call, reserved for tipping the solver
/// @param hintdices Bytes representing indexes of the call objects
/// @param flashLoanData Bytes representing associated data with the verify call, reserved for tipping the solver
/// @param callObjs The calls to be executed
/// @param returnsBytes The returns to be compareed with return values from call obj execution
/// @param associatedData To be used in the execute and verify call, also reserved for tipping the solver
/// @param flashLoanData with information about aquiring the flash loan
function executeAndVerify(
bytes calldata callsBytes,
bytes calldata returnsBytes,
bytes calldata associatedData,
bytes calldata hintdices,
bytes calldata flashLoanData
CallObject[] calldata callObjs,
ReturnObject[] calldata returnsBytes,
AdditionalData[] calldata associatedData,
FlashLoanData calldata flashLoanData
) external payable onlyPortalClosed {
_setupExecutionData(callsBytes, returnsBytes, associatedData, hintdices);
FlashLoanData memory _flashLoanData = abi.decode(flashLoanData, (FlashLoanData));
IFlashLoan(_flashLoanData.provider).flashLoan(
address(this), _flashLoanData.amountA, _flashLoanData.amountB, callsBytes
_setupExecutionData(callObjs, returnsBytes, associatedData);
bytes memory data = abi.encode(callObjs);
IFlashLoan(flashLoanData.provider).flashLoan(
address(this), flashLoanData.amountA, flashLoanData.amountB, data
);
}

Expand All @@ -99,7 +97,7 @@ contract CallBreaker is CallBreakerStorage {
* @param amountA The amount of tokens lent.
* @param tokenB The second loan currency.
* @param amountB The amount of tokens lent.
* @param data the execute and verify data
* @param data The encoded calls to be executed
* @return true if the function executed successfully
*/
function onFlashLoan(
Expand All @@ -108,34 +106,30 @@ contract CallBreaker is CallBreakerStorage {
uint256 amountA,
address tokenB,
uint256 amountB,
bytes calldata data
bytes memory data
) external onlyPortalOpen returns (bool) {
emit CallBreakerFlashFunds(tokenA, amountA, tokenB, amountB);
CallObject[] memory calls = abi.decode(data, (CallObject[]));

_executeAndVerifyCalls(calls);
_executeAndVerifyCalls(data);
IERC20(tokenA).approve(msg.sender, amountA);
IERC20(tokenB).approve(msg.sender, amountB);
return true;
}

/// @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.
/// @param callObjWithIndex The call to be executed, structured as a CallObjectWithIndex.
/// @return The return value from the record of return values.
function getReturnValue(bytes calldata input) external view returns (bytes memory) {
// Decode the input to obtain the CallObject and calculate a unique ID representing the call-return pair
CallObjectWithIndex memory callObjWithIndex = abi.decode(input, (CallObjectWithIndex));
function getReturnValue(CallObjectWithIndex calldata callObjWithIndex) external view returns (bytes memory) {
ReturnObject memory thisReturn = _getReturn(callObjWithIndex.index);
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.
/// @param index The index of call to be executed.
/// @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;
}
Expand Down Expand Up @@ -226,32 +220,28 @@ contract CallBreaker is CallBreakerStorage {
}

function _setupExecutionData(
bytes calldata callsBytes,
bytes calldata returnsBytes,
bytes calldata associatedData,
bytes calldata hintdices
CallObject[] calldata calls,
ReturnObject[] calldata returnValues,
AdditionalData[] calldata associatedData
) internal returns (CallObject[] memory) {
if (msg.sender != tx.origin) {
revert MustBeEOA();
}

CallObject[] memory calls = abi.decode(callsBytes, (CallObject[]));
ReturnObject[] memory returnValues = abi.decode(returnsBytes, (ReturnObject[]));

if (calls.length != returnValues.length) {
revert LengthMismatch();
}

_setPortalOpen(calls, returnValues);
_populateCallsAndReturnValues(calls, returnValues);
_populateAssociatedDataStore(associatedData);
_populateHintdices(hintdices);
_populateCallIndices();

return calls;
}

function _executeAndVerifyCalls(CallObject[] memory calls) internal {
function _executeAndVerifyCalls(bytes memory callData) internal {
CallObject[] memory calls = abi.decode(callData, (CallObject[]));
uint256 l = calls.length;
for (uint256 i = 0; i < l; i++) {
_setCurrentlyExecutingCallIndex(i);
Expand Down Expand Up @@ -289,6 +279,11 @@ contract CallBreaker is CallBreakerStorage {
for (uint256 i = 0; i < calls.length; i++) {
callStore.push().store(calls[i]);
returnStore.push(returnValues[i]);

// Populating hintdices value while setting execution data
bytes32 key = keccak256(abi.encode(calls[i]));
hintdicesStoreKeyList.push(key);
hintdicesStore[key].push(i);
}
}

Expand All @@ -302,27 +297,15 @@ contract CallBreaker is CallBreakerStorage {
}

/// @notice Populates the associatedDataStore with a list of key-value pairs
/// @param encodedData The abi-encoded list of (bytes32, bytes32) key-value pairs
function _populateAssociatedDataStore(bytes memory encodedData) internal {
AdditionalData[] memory associatedData = abi.decode(encodedData, (AdditionalData[]));

/// @param associatedData The abi-encoded list of (bytes32, bytes32) key-value pairs
function _populateAssociatedDataStore(AdditionalData[] memory associatedData) internal {
uint256 l = associatedData.length;
for (uint256 i = 0; i < l; i++) {
associatedDataKeyList.push(associatedData[i].key);
associatedDataStore[associatedData[i].key] = associatedData[i].value;
}
}

function _populateHintdices(bytes memory encodedData) internal {
AdditionalData[] memory hintDices = abi.decode(encodedData, (AdditionalData[]));

uint256 l = hintDices.length;
for (uint256 i = 0; i < l; i++) {
hintdicesStoreKeyList.push(hintDices[i].key);
hintdicesStore[hintDices[i].key].push(abi.decode(hintDices[i].value, (uint256))); // Note: the value being pushed can repeat itself if given wrong data
}
}

function _expectCallAt(CallObject memory callObj, uint256 index) internal view {
if (keccak256(abi.encode(_getCall(index))) != keccak256(abi.encode(callObj))) {
revert CallPositionFailed(callObj, index);
Expand Down
Loading

0 comments on commit e869ae7

Please sign in to comment.