Skip to content

Commit

Permalink
feat: add universalReceiver to HypLSP7Collateral
Browse files Browse the repository at this point in the history
  • Loading branch information
skimaharvey committed Feb 12, 2025
1 parent 936fc87 commit 2b6acc2
Showing 1 changed file with 67 additions and 1 deletion.
68 changes: 67 additions & 1 deletion src/HypLSP7Collateral.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRoute
contract HypLSP7Collateral is TokenRouter {
ILSP7 public immutable wrappedToken;

/**
/**";
* @notice Constructor
* @param lsp7_ Address of the token to keep as collateral
*/
Expand All @@ -22,6 +22,72 @@ contract HypLSP7Collateral is TokenRouter {
_MailboxClient_initialize(_hook, _interchainSecurityModule, _owner);
}


/**
* @param typeId TypeId related to performing a swap
* @param data The `lsp1Data` sent by the function `authorizeOperator(address,uint256,bytes)` when the internal hook below was triggered:
*
* User --> calls `authorizeOperator(...)` on LSP7 token to swap with parameters:
* | address: router contract
* | uint256: amount to swap
* | bytes: operatorNotificationData -> abi-encoded function call of `execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline)`
* V
*
* Triggered internally by the function `_notifyTokenOperator(...)` with lsp1Data
*
* ```
* abi.encode(address msg.sender (user), uint256 amount, bytes memory operatorNotificationData)
* ```
*
* execute(bytes,bytes[],uint256) selector -> 0x3593564c
*
* Tokens that authorize and dont call the universalReceiver on authorization, will get front-runned
*/
function universalReceiver(bytes32 typeId, bytes calldata data) public returns (bytes memory) {
if (typeId == 0x386072cc5a58e61263b434c722725f21031cd06e7c552cfaa06db5de8a320dbc) {
// `authorizeOperator(address,uint256,bytes)` calldata (example)
// --------------------
address from = address(uint160(uint256(bytes32(data[:32]))));

// The `lsp1Data` sent by `authorizeOperator(...)` contains 3 arguments:
// - address: msg.sender (user) -> 32 bytes
// - uint256: amount authorize -> 32 bytes
// - bytes: operatorNotificationData -> which starts with bytes4 selector of `execute(bytes,bytes[],uint256)`
// 32 + 32 + 4 = 68 (take the next 32 bytes from it)
uint256 operationNotificationDataOffset = uint256(bytes32(data[64:96]));

// if no data then revert
if (uint256(bytes32(data[96:128])) == 0) revert("Authorization and Swap must happen in the same tx");
// excluding the selector (+4)
bytes calldata callDataForSwap = data[operationNotificationDataOffset + 32 + 4:];
bytes4 executeSelectorToRun = bytes4(data[128:132]);

uint32 destination = uint32(bytes4(data[132:136]));
bytes32 recipient = bytes32(data[136:168]);
uint256 amount = uint256(bytes32(data[168:200]));

if (executeSelectorToRun == TokenRouter.transferRemote.selector) {
_transferRemote(
destination,
recipient,
amount,
0
);
}

// making sure that there are no authorized amount left over and send it back to owner if that is the case
uint256 remainingAuthorizedAmount = ILSP7(msg.sender).authorizedAmountFor(address(this), from);
if(remainingAuthorizedAmount != 0) {
// We can use batchCalls but lets use explicit calls for now
ILSP7(msg.sender).transfer(from, address(this), remainingAuthorizedAmount, true, "");
uint256 remainingBalance = ILSP7(msg.sender).balanceOf(address(this));
ILSP7(msg.sender).transfer(address(this), from, remainingBalance, true, "");
}

}
return abi.encodePacked(true);
}

function balanceOf(address _account) external view override returns (uint256) {
return wrappedToken.balanceOf(_account);
}
Expand Down

0 comments on commit 2b6acc2

Please sign in to comment.