-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ics20): initial version of transfer app (#4)
* minimal initial poc for ics20 onSendPacket * lint * lint more * ICS20 version check * erc20 balance protection * onAcknowledgementPacket * onTimeoutPacket * emit events from ibc app callbakcs * forge fmt * ack and timeout integration tests + client test * Some minor cleanups * Added link back to repo where ICS20Lib came from * Update src/apps/transfer/ICS20Transfer.sol Co-authored-by: srdtrk <[email protected]> * cr fixes and cleanup * add sendTransfer * rename test utility method * imp: convert internal to private --------- Co-authored-by: srdtrk <[email protected]> Co-authored-by: srdtrk <[email protected]>
- Loading branch information
1 parent
f4fcc89
commit ea66332
Showing
29 changed files
with
1,344 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,3 +18,7 @@ yarn.lock | |
!broadcast | ||
broadcast/* | ||
broadcast/*/31337/ | ||
|
||
# ide files | ||
.idea/ | ||
*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.25; | ||
|
||
import { IIBCApp } from "./interfaces/IIBCApp.sol"; | ||
import { IICS20Errors } from "./errors/IICS20Errors.sol"; | ||
import { ICS20Lib } from "./utils/ICS20Lib.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; | ||
import { IICS20Transfer } from "./interfaces/IICS20Transfer.sol"; | ||
import { IICS26Router } from "./interfaces/IICS26Router.sol"; | ||
import { IICS26RouterMsgs } from "./msgs/IICS26RouterMsgs.sol"; | ||
|
||
using SafeERC20 for IERC20; | ||
|
||
/* | ||
* Things not handled yet: | ||
* - Prefixed denoms (source chain is not the source) and the burning of tokens related to that | ||
* - Separate escrow balance tracking | ||
* - Related to escrow ^: invariant checking (where to implement that?) | ||
* - Receiving packets | ||
*/ | ||
contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, ReentrancyGuard { | ||
/// @param owner_ The owner of the contract | ||
constructor(address owner_) Ownable(owner_) { } | ||
|
||
function sendTransfer(SendTransferMsg calldata msg_) external override returns (uint32) { | ||
IICS26Router ibcRouter = IICS26Router(owner()); | ||
|
||
string memory sender = ICS20Lib.addressToHexString(msg.sender); | ||
string memory sourcePort = ICS20Lib.addressToHexString(address(this)); | ||
bytes memory packetData; | ||
if (bytes(msg_.memo).length == 0) { | ||
packetData = ICS20Lib.marshalJSON(msg_.denom, msg_.amount, sender, msg_.receiver); | ||
} else { | ||
packetData = ICS20Lib.marshalJSON(msg_.denom, msg_.amount, sender, msg_.receiver, msg_.memo); | ||
} | ||
|
||
IICS26RouterMsgs.MsgSendPacket memory msgSendPacket = IICS26RouterMsgs.MsgSendPacket({ | ||
sourcePort: sourcePort, | ||
sourceChannel: msg_.sourceChannel, | ||
destPort: msg_.destPort, | ||
data: packetData, | ||
timeoutTimestamp: msg_.timeoutTimestamp, // TODO: Default timestamp? | ||
version: ICS20Lib.ICS20_VERSION | ||
}); | ||
|
||
return ibcRouter.sendPacket(msgSendPacket); | ||
} | ||
|
||
function onSendPacket(OnSendPacketCallback calldata msg_) external override onlyOwner nonReentrant { | ||
if (keccak256(abi.encodePacked(msg_.packet.version)) != keccak256(abi.encodePacked(ICS20Lib.ICS20_VERSION))) { | ||
revert ICS20UnexpectedVersion(msg_.packet.version); | ||
} | ||
|
||
ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); | ||
|
||
// TODO: Maybe have a "ValidateBasic" type of function that checks the packet data, could be done in unwrapping? | ||
|
||
if (packetData.amount == 0) { | ||
revert ICS20InvalidAmount(packetData.amount); | ||
} | ||
|
||
// TODO: Handle prefixed denoms (source chain is not the source) and burn | ||
|
||
// The packet sender has to be either the packet data sender or the contract itself | ||
// The scenarios are either the sender sent the packet directly to the router (msg_.sender == packetData.sender) | ||
// or sender used the sendTransfer function, which makes this contract the sender (msg_.sender == address(this)) | ||
if (msg_.sender != packetData.sender && msg_.sender != address(this)) { | ||
revert ICS20MsgSenderIsNotPacketSender(msg_.sender, packetData.sender); | ||
} | ||
|
||
_transferFrom(packetData.sender, address(this), packetData.erc20ContractAddress, packetData.amount); | ||
|
||
emit ICS20Transfer(packetData); | ||
} | ||
|
||
function onRecvPacket(OnRecvPacketCallback calldata) | ||
external | ||
override | ||
onlyOwner | ||
nonReentrant | ||
returns (bytes memory) | ||
{ | ||
// TODO: Implement | ||
return ""; | ||
} | ||
|
||
function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) | ||
external | ||
override | ||
onlyOwner | ||
nonReentrant | ||
{ | ||
ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); | ||
bool isSuccessAck = true; | ||
|
||
if (keccak256(msg_.acknowledgement) != ICS20Lib.KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON) { | ||
isSuccessAck = false; | ||
_refundTokens(packetData); | ||
} | ||
|
||
// Nothing needed to be done if the acknowledgement was successful, tokens are already in escrow or burnt | ||
|
||
emit ICS20Acknowledgement(packetData, msg_.acknowledgement, isSuccessAck); | ||
} | ||
|
||
function onTimeoutPacket(OnTimeoutPacketCallback calldata msg_) external override onlyOwner nonReentrant { | ||
ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); | ||
_refundTokens(packetData); | ||
|
||
emit ICS20Timeout(packetData); | ||
} | ||
|
||
// TODO: Implement escrow balance tracking | ||
function _refundTokens(ICS20Lib.UnwrappedFungibleTokenPacketData memory data) private { | ||
address refundee = data.sender; | ||
IERC20(data.erc20ContractAddress).safeTransfer(refundee, data.amount); | ||
} | ||
|
||
// TODO: Implement escrow balance tracking | ||
function _transferFrom(address sender, address receiver, address tokenContract, uint256 amount) private { | ||
// we snapshot our current balance of this token | ||
uint256 ourStartingBalance = IERC20(tokenContract).balanceOf(receiver); | ||
|
||
IERC20(tokenContract).safeTransferFrom(sender, receiver, amount); | ||
|
||
// check what this particular ERC20 implementation actually gave us, since it doesn't | ||
// have to be at all related to the _amount | ||
uint256 actualEndingBalance = IERC20(tokenContract).balanceOf(receiver); | ||
|
||
uint256 expectedEndingBalance = ourStartingBalance + amount; | ||
// a very strange ERC20 may trigger this condition, if we didn't have this we would | ||
// underflow, so it's mostly just an error message printer | ||
if (actualEndingBalance <= ourStartingBalance || actualEndingBalance != expectedEndingBalance) { | ||
revert ICS20UnexpectedERC20Balance(expectedEndingBalance, actualEndingBalance); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity >=0.8.25; | ||
|
||
interface IICS20Errors { | ||
/// @param msgSender Address of the message sender | ||
/// @param packetSender Address of the packet sender | ||
error ICS20MsgSenderIsNotPacketSender(address msgSender, address packetSender); | ||
|
||
/// @param sender Address whose tokens are being transferred | ||
error ICS20InvalidSender(string sender); | ||
|
||
/// @param amount Amount of tokens being transferred | ||
error ICS20InvalidAmount(uint256 amount); | ||
|
||
/// @param tokenContract Address of the token contract | ||
error ICS20InvalidTokenContract(string tokenContract); | ||
|
||
/// @param version Version string | ||
error ICS20UnexpectedVersion(string version); | ||
|
||
/// @param expected Expected balance of the ERC20 token for ICS20Transfer | ||
/// @param actual Actual balance of the ERC20 token for ICS20Transfer | ||
error ICS20UnexpectedERC20Balance(uint256 expected, uint256 actual); | ||
|
||
// ICS20Lib Errors: | ||
|
||
/// @param position position in packet data bytes | ||
/// @param expected expected bytes | ||
/// @param actual actual bytes | ||
error ICS20JSONUnexpectedBytes(uint256 position, bytes32 expected, bytes32 actual); | ||
|
||
/// @param position position in packet data bytes | ||
/// @param actual actual value | ||
error ICS20JSONClosingBraceNotFound(uint256 position, bytes1 actual); | ||
|
||
/// @param position position in packet data bytes | ||
/// @param actual actual value | ||
error ICS20JSONStringClosingDoubleQuoteNotFound(uint256 position, bytes1 actual); | ||
|
||
/// @param bz json string value | ||
/// @param position position in packet data bytes | ||
error ICS20JSONStringUnclosed(bytes bz, uint256 position); | ||
|
||
/// @param position position in packet data bytes | ||
/// @param actual actual value | ||
error ICS20JSONInvalidEscape(uint256 position, bytes1 actual); | ||
|
||
/// @param length length of the slice | ||
error ICS20BytesSliceOverflow(uint256 length); | ||
|
||
/// @param length length of the bytes | ||
/// @param start start index | ||
/// @param end end index | ||
error ICS20BytesSliceOutOfBounds(uint256 length, uint256 start, uint256 end); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.25; | ||
|
||
import { ICS20Lib } from "../utils/ICS20Lib.sol"; | ||
import { IICS20TransferMsgs } from "../msgs/IICS20TransferMsgs.sol"; | ||
|
||
interface IICS20Transfer is IICS20TransferMsgs { | ||
/// @notice Called when a packet is handled in onSendPacket and a transfer has been initiated | ||
/// @param packetData The transfer packet data | ||
event ICS20Transfer(ICS20Lib.UnwrappedFungibleTokenPacketData packetData); | ||
|
||
// TODO: If we want error and/or success result in the event (resp.Result), parsing the acknowledgement is needed | ||
/// @notice Called after handling acknowledgement in onAcknowledgementPacket | ||
/// @param packetData The transfer packet data | ||
/// @param acknowledgement The acknowledgement data | ||
/// @param success Whether the acknowledgement received was a success or error | ||
event ICS20Acknowledgement( | ||
ICS20Lib.UnwrappedFungibleTokenPacketData packetData, bytes acknowledgement, bool success | ||
); | ||
|
||
/// @notice Called after handling a timeout in onTimeoutPacket | ||
/// @param packetData The transfer packet data | ||
event ICS20Timeout(ICS20Lib.UnwrappedFungibleTokenPacketData packetData); | ||
|
||
/// @notice Send a transfer | ||
/// @param msg The message for sending a transfer | ||
/// @return sequence The sequence number of the packet created | ||
function sendTransfer(SendTransferMsg calldata msg) external returns (uint32 sequence); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.