Skip to content

Commit 98b049c

Browse files
committed
add port to ICS20Transfer constructor
Signed-off-by: Jun Kimura <jun.kimura@datachain.jp>
1 parent 2ca15e9 commit 98b049c

File tree

9 files changed

+227
-138
lines changed

9 files changed

+227
-138
lines changed

.gas-snapshot

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,38 +44,42 @@ TestICS04Handshake:testInvalidChanOpenAck() (gas: 2327618)
4444
TestICS04Handshake:testInvalidChanOpenConfirm() (gas: 2402634)
4545
TestICS04Handshake:testInvalidChanOpenInit() (gas: 1677184)
4646
TestICS04Handshake:testInvalidChanOpenTry() (gas: 1692266)
47-
TestICS04Packet:testAcknowledgementPacket() (gas: 3133839)
48-
TestICS04Packet:testInvalidSendPacket() (gas: 3317459)
49-
TestICS04Packet:testRecvPacket() (gas: 9561916)
50-
TestICS04Packet:testRecvPacketTimeoutHeight() (gas: 3083947)
51-
TestICS04Packet:testRecvPacketTimeoutTimestamp() (gas: 3107997)
52-
TestICS04Packet:testSendPacket() (gas: 4425319)
53-
TestICS04Packet:testTimeoutOnClose() (gas: 3335905)
54-
TestICS04Upgrade:testCrossingHelloInconsistentVersions() (gas: 9761065)
55-
TestICS04Upgrade:testUpgradeAuthorityCancel() (gas: 45179742)
56-
TestICS04Upgrade:testUpgradeCannotCancelWithOldErrorReceipt() (gas: 3309916)
57-
TestICS04Upgrade:testUpgradeCannotRecvNextUpgradePacket() (gas: 5142516)
58-
TestICS04Upgrade:testUpgradeCounterpartyAdvanceNextSequenceBeforeOpen() (gas: 5101888)
59-
TestICS04Upgrade:testUpgradeCrossingHelloIncompatibleProposals() (gas: 4859793)
60-
TestICS04Upgrade:testUpgradeFull() (gas: 55736320)
61-
TestICS04Upgrade:testUpgradeInit() (gas: 2937759)
62-
TestICS04Upgrade:testUpgradeNoChanges() (gas: 2354426)
63-
TestICS04Upgrade:testUpgradeNotUpgradableModule() (gas: 3470809)
64-
TestICS04Upgrade:testUpgradeOutOfSync() (gas: 3741133)
65-
TestICS04Upgrade:testUpgradeRelaySuccessAtCounterpartyFlushComplete() (gas: 5115114)
66-
TestICS04Upgrade:testUpgradeRelaySuccessAtFlushing() (gas: 5487347)
67-
TestICS04Upgrade:testUpgradeSendPacketFailAtFlushingOrFlushComplete() (gas: 3920258)
68-
TestICS04Upgrade:testUpgradeTimeoutAbortAck() (gas: 17345060)
69-
TestICS04Upgrade:testUpgradeTimeoutAbortConfirm() (gas: 20945649)
70-
TestICS04Upgrade:testUpgradeTimeoutUpgrade() (gas: 69105350)
71-
TestICS04Upgrade:testUpgradeToOrdered() (gas: 53030759)
72-
TestICS04Upgrade:testUpgradeToUnordered() (gas: 42323192)
47+
TestICS04Packet:testAcknowledgementPacket() (gas: 3133817)
48+
TestICS04Packet:testInvalidSendPacket() (gas: 3317437)
49+
TestICS04Packet:testRecvPacket() (gas: 9561861)
50+
TestICS04Packet:testRecvPacketTimeoutHeight() (gas: 3083925)
51+
TestICS04Packet:testRecvPacketTimeoutTimestamp() (gas: 3107975)
52+
TestICS04Packet:testSendPacket() (gas: 4425297)
53+
TestICS04Packet:testTimeoutOnClose() (gas: 3335859)
54+
TestICS04Upgrade:testCrossingHelloInconsistentVersions() (gas: 9761029)
55+
TestICS04Upgrade:testUpgradeAuthorityCancel() (gas: 45179694)
56+
TestICS04Upgrade:testUpgradeCannotCancelWithOldErrorReceipt() (gas: 3309912)
57+
TestICS04Upgrade:testUpgradeCannotRecvNextUpgradePacket() (gas: 5142512)
58+
TestICS04Upgrade:testUpgradeCounterpartyAdvanceNextSequenceBeforeOpen() (gas: 5101884)
59+
TestICS04Upgrade:testUpgradeCrossingHelloIncompatibleProposals() (gas: 4859789)
60+
TestICS04Upgrade:testUpgradeFull() (gas: 55736252)
61+
TestICS04Upgrade:testUpgradeInit() (gas: 2937755)
62+
TestICS04Upgrade:testUpgradeNoChanges() (gas: 2354422)
63+
TestICS04Upgrade:testUpgradeNotUpgradableModule() (gas: 3470805)
64+
TestICS04Upgrade:testUpgradeOutOfSync() (gas: 3741129)
65+
TestICS04Upgrade:testUpgradeRelaySuccessAtCounterpartyFlushComplete() (gas: 5115110)
66+
TestICS04Upgrade:testUpgradeRelaySuccessAtFlushing() (gas: 5487343)
67+
TestICS04Upgrade:testUpgradeSendPacketFailAtFlushingOrFlushComplete() (gas: 3920254)
68+
TestICS04Upgrade:testUpgradeTimeoutAbortAck() (gas: 17345056)
69+
TestICS04Upgrade:testUpgradeTimeoutAbortConfirm() (gas: 20945645)
70+
TestICS04Upgrade:testUpgradeTimeoutUpgrade() (gas: 69105286)
71+
TestICS04Upgrade:testUpgradeToOrdered() (gas: 53030719)
72+
TestICS04Upgrade:testUpgradeToUnordered() (gas: 42323160)
7373
TestICS04UpgradeApp:testUpgradeAuthorizationChanneNotFound() (gas: 62062)
7474
TestICS04UpgradeApp:testUpgradeAuthorizationRePropose() (gas: 2376902)
7575
TestICS04UpgradeApp:testUpgradeAuthorizationRemove() (gas: 2357348)
76-
TestICS20:testAddressToHex(address) (runs: 256, μ: 26910, ~: 27088)
77-
TestICS20:testHexToAddress(string) (runs: 256, μ: 4658, ~: 4617)
78-
TestICS20:testIsEscapedString() (gas: 64753)
79-
TestICS20:testMarshaling() (gas: 180017)
80-
TestICS20:testParseAmount(uint256) (runs: 256, μ: 31599, ~: 27438)
81-
TestICS20:testParseUint256String() (gas: 26745)
76+
TestICS20Lib:testAddressToHex(address) (runs: 256, μ: 26916, ~: 27088)
77+
TestICS20Lib:testHexToAddress(string) (runs: 256, μ: 4658, ~: 4617)
78+
TestICS20Lib:testIsEscapedString() (gas: 64753)
79+
TestICS20Lib:testMarshaling() (gas: 180017)
80+
TestICS20Lib:testParseAmount(uint256) (runs: 256, μ: 31619, ~: 27869)
81+
TestICS20Lib:testParseUint256String() (gas: 26745)
82+
TestICS20Transfer:testDeposit() (gas: 404927)
83+
TestICS20Transfer:testDepositTransferWithdraw() (gas: 321969)
84+
TestICS20Transfer:testRelay() (gas: 7588605)
85+
TestICS20Transfer:testWithdraw() (gas: 416732)

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ lint:
3232

3333
.PHONY: test
3434
test:
35-
TEST_UPGRADEABLE=$(TEST_UPGRADEABLE) $(FORGE) test -vvvv --gas-report --isolate --use solc:$(SOLC_VERSION) $(FORGE_SNAPSHOT_OPTION)
35+
TEST_UPGRADEABLE=$(TEST_UPGRADEABLE) $(FORGE) test -vvvv --gas-report --isolate --use solc:$(SOLC_VERSION)
3636

3737
.PHONY: snapshot
3838
snapshot:
39-
$(FORGE) snapshot -vvvv --gas-report --isolate --use solc:$(SOLC_VERSION) $(FORGE_SNAPSHOT_OPTION)
39+
$(FORGE) snapshot -vvvv --gas-report --isolate --use solc:$(SOLC_VERSION)
4040

4141
.PHONY: coverage
4242
coverage:

contracts/apps/20-transfer/ICS20Lib.sol

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,10 @@
22
pragma solidity ^0.8.20;
33

44
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
5+
import {Height} from "../../proto/Client.sol";
56
import {IICS20Errors} from "./IICS20Errors.sol";
67

78
library ICS20Lib {
8-
/**
9-
* @dev PacketData is defined in [ICS-20](https://github.com/cosmos/ibc/tree/main/spec/app/ics-020-fungible-token-transfer).
10-
*/
11-
struct PacketData {
12-
string denom;
13-
string sender;
14-
string receiver;
15-
uint256 amount;
16-
string memo;
17-
}
18-
199
bytes internal constant SUCCESSFUL_ACKNOWLEDGEMENT_JSON = bytes('{"result":"AQ=="}');
2010
bytes internal constant FAILED_ACKNOWLEDGEMENT_JSON = bytes('{"error":"failed"}');
2111
bytes32 internal constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256(SUCCESSFUL_ACKNOWLEDGEMENT_JSON);
@@ -33,6 +23,25 @@ library ICS20Lib {
3323

3424
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
3525

26+
/**
27+
* @dev PacketData is defined in [ICS-20](https://github.com/cosmos/ibc/tree/main/spec/app/ics-020-fungible-token-transfer).
28+
*/
29+
struct PacketData {
30+
string denom;
31+
string sender;
32+
string receiver;
33+
uint256 amount;
34+
string memo;
35+
}
36+
37+
/**
38+
* @dev Either `height` or `timestampNanos` must be set.
39+
*/
40+
struct Timeout {
41+
Height.Data height;
42+
uint64 timestampNanos;
43+
}
44+
3645
/**
3746
* @dev marshalUnsafeJSON marshals PacketData into JSON bytes without escaping.
3847
* `memo` field is omitted if it is empty.
@@ -147,6 +156,23 @@ library ICS20Lib {
147156
return pd;
148157
}
149158

159+
/**
160+
* @dev timeout returns a Timeout struct with the given height.
161+
*/
162+
function timeout(uint64 revisionNumber, uint64 revisionHeight) internal pure returns (Timeout memory) {
163+
return Timeout({
164+
height: Height.Data({revision_number: revisionNumber, revision_height: revisionHeight}),
165+
timestampNanos: 0
166+
});
167+
}
168+
169+
/**
170+
* @dev timeout returns a Timeout struct with the given timestamp.
171+
*/
172+
function timeout(uint64 timestampNanos) internal pure returns (Timeout memory) {
173+
return Timeout({height: Height.Data({revision_number: 0, revision_height: 0}), timestampNanos: timestampNanos});
174+
}
175+
150176
/**
151177
* @dev parseUint256String parses `bz` from a position `pos` to produce a uint256 value.
152178
* The parse will stop parsing when it encounters a non-digit character.

contracts/apps/20-transfer/ICS20Transfer.sol

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,57 @@
22
pragma solidity ^0.8.20;
33

44
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import {ShortString, ShortStrings} from "@openzeppelin/contracts/utils/ShortStrings.sol";
56
import {IBCAppBase} from "../commons/IBCAppBase.sol";
67
import {Packet} from "../../core/04-channel/IIBCChannel.sol";
78
import {IIBCModule} from "../../core/26-router/IIBCModule.sol";
8-
import {Height} from "../../proto/Client.sol";
99
import {Channel} from "../../proto/Channel.sol";
1010
import {ICS20Lib} from "./ICS20Lib.sol";
1111
import {IICS20Errors} from "./IICS20Errors.sol";
1212
import {IIBCHandler} from "../../core/25-handler/IIBCHandler.sol";
1313

1414
contract ICS20Transfer is IBCAppBase, IICS20Errors {
15-
string public constant ICS20_VERSION = "ics20-1";
15+
using ShortStrings for string;
16+
using ShortStrings for ShortString;
1617

17-
// mapping from denomination to account balances
18-
mapping(string denom => mapping(address account => uint256 balance)) internal _balances;
18+
/// @dev ICS20 version
19+
string public constant ICS20_VERSION = "ics20-1";
1920

2021
/// @dev IIBCHandler instance
2122
IIBCHandler internal immutable ibcHandler;
23+
/// @dev port identifier
24+
ShortString internal immutable port;
25+
26+
/// @dev balance mapping for the token
27+
mapping(string denom => mapping(address account => uint256 balance)) internal _balances;
2228

2329
/// @param ibcHandler_ IIBCHandler instance
24-
constructor(IIBCHandler ibcHandler_) {
30+
/// @param port_ port identifier
31+
constructor(IIBCHandler ibcHandler_, string memory port_) {
2532
ibcHandler = ibcHandler_;
33+
port = port_.toShortString();
2634
}
2735

2836
// ------------------------------ Public Functions ------------------------------ //
2937

3038
/**
3139
* @dev sendTransfer sends a transfer packet to the destination chain.
40+
* @param sourceChannel source channel of the packet
3241
* @param denom denomination of the token. It can assume the denom string is escaped or not required to be escaped.
3342
* @param amount amount of the token
34-
* @param receiver receiver address on the destination chain
35-
* @param sourcePort source port of the packet
36-
* @param sourceChannel source channel of the packet
37-
* @param timeoutHeight timeout height of the packet
43+
* @param receiver receiver address on the destination chain. This must be a valid address format per destination chain.
3844
*/
3945
function sendTransfer(
46+
string calldata sourceChannel,
4047
string calldata denom,
4148
uint256 amount,
4249
string calldata receiver,
43-
string calldata sourcePort,
44-
string calldata sourceChannel,
45-
uint64 timeoutHeight
50+
ICS20Lib.Timeout calldata timeout
4651
) external returns (uint64) {
4752
if (!ICS20Lib.isEscapedJSONString(receiver)) {
4853
revert ICS20InvalidReceiverAddress(receiver);
4954
}
55+
string memory sourcePort = port.toString();
5056
bytes memory denomPrefix = ICS20Lib.denomPrefix(sourcePort, sourceChannel);
5157
bytes memory denomBytes = bytes(denom);
5258
if (
@@ -62,28 +68,24 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
6268
_burnVoucher(_msgSender(), denom, amount);
6369
}
6470
bytes memory packetData = ICS20Lib.marshalJSON(denom, amount, encodeAddress(_msgSender()), receiver);
65-
return ibcHandler.sendPacket(
66-
sourcePort, sourceChannel, Height.Data({revision_number: 0, revision_height: timeoutHeight}), 0, packetData
67-
);
71+
return ibcHandler.sendPacket(sourcePort, sourceChannel, timeout.height, timeout.timestampNanos, packetData);
6872
}
6973

7074
/**
7175
* @dev depositSendTransfer sends a transfer packet to the destination chain after depositing the token.
76+
* @param sourceChannel source channel of the packet
7277
* @param tokenContract address of the token contract
7378
* @param amount amount of the token
74-
* @param receiver receiver address on the destination chain
75-
* @param sourcePort source port of the packet
76-
* @param sourceChannel source channel of the packet
77-
* @param timeoutHeight timeout height of the packet
79+
* @param receiver receiver address on the destination chain. This must be a valid address format per destination chain.
7880
*/
7981
function depositSendTransfer(
82+
string calldata sourceChannel,
8083
address tokenContract,
8184
uint256 amount,
8285
string calldata receiver,
83-
string calldata sourcePort,
84-
string calldata sourceChannel,
85-
uint64 timeoutHeight
86+
ICS20Lib.Timeout calldata timeout
8687
) external returns (uint64) {
88+
string memory sourcePort = port.toString();
8789
if (!ICS20Lib.isEscapedJSONString(receiver)) {
8890
revert ICS20InvalidReceiverAddress(receiver);
8991
}
@@ -97,42 +99,40 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
9799
_mintVoucher(getVoucherEscrow(sourceChannel), tokenContract, amount);
98100
bytes memory packetData =
99101
ICS20Lib.marshalJSON(ICS20Lib.addressToHexString(tokenContract), amount, encodeAddress(sender), receiver);
100-
return ibcHandler.sendPacket(
101-
sourcePort, sourceChannel, Height.Data({revision_number: 0, revision_height: timeoutHeight}), 0, packetData
102-
);
102+
return ibcHandler.sendPacket(sourcePort, sourceChannel, timeout.height, timeout.timestampNanos, packetData);
103103
}
104104

105105
/**
106106
* @dev deposit deposits the ERC20 token to the contract.
107+
* @param to address to deposit the token
107108
* @param tokenContract address of the token contract
108109
* @param amount amount of the token
109-
* @param to address to deposit the token
110110
*/
111111
function deposit(address to, address tokenContract, uint256 amount) public {
112112
if (tokenContract == address(0)) {
113113
revert ICS20InvalidTokenContract(tokenContract);
114114
}
115-
address from = _msgSender();
116-
if (!IERC20(tokenContract).transferFrom(from, address(this), amount)) {
117-
revert ICS20FailedERC20Transfer(tokenContract, from, address(this), amount);
115+
address sender = _msgSender();
116+
if (!IERC20(tokenContract).transferFrom(sender, address(this), amount)) {
117+
revert ICS20FailedERC20Transfer(tokenContract, sender, address(this), amount);
118118
}
119119
_mintVoucher(to, tokenContract, amount);
120120
}
121121

122122
/**
123123
* @dev withdraw withdraws the ERC20 token from the contract.
124+
* @param to address to withdraw the token
124125
* @param tokenContract address of the token contract
125126
* @param amount amount of the token
126-
* @param to address to withdraw the token
127127
*/
128128
function withdraw(address to, address tokenContract, uint256 amount) public {
129129
if (tokenContract == address(0)) {
130130
revert ICS20InvalidTokenContract(tokenContract);
131131
}
132-
address from = _msgSender();
133-
_burnVoucher(from, ICS20Lib.addressToHexString(tokenContract), amount);
132+
address sender = _msgSender();
133+
_burnVoucher(sender, ICS20Lib.addressToHexString(tokenContract), amount);
134134
if (!IERC20(tokenContract).transfer(to, amount)) {
135-
revert ICS20FailedERC20TransferFrom(tokenContract, from, address(this), to, amount);
135+
revert ICS20FailedERC20TransferFrom(tokenContract, sender, address(this), to, amount);
136136
}
137137
}
138138

@@ -171,6 +171,7 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
171171
/**
172172
* @dev getVoucherEscrow returns the voucher escrow address for the given channel.
173173
* @param channelId channel identifier
174+
* @return voucher escrow address
174175
*/
175176
function getVoucherEscrow(string calldata channelId) public view virtual returns (address) {
176177
return address(uint160(uint256(keccak256(abi.encode(address(this), channelId)))));
@@ -266,6 +267,9 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
266267
onlyIBC
267268
returns (address, string memory)
268269
{
270+
if (!_equal(msg_.portId.toShortString(), port)) {
271+
revert ICS20UnexpectedPort(msg_.portId, port.toString());
272+
}
269273
if (msg_.order != Channel.Order.ORDER_UNORDERED) {
270274
revert IBCModuleChannelOrderNotAllowed(msg_.portId, msg_.channelId, msg_.order);
271275
}
@@ -286,6 +290,9 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
286290
onlyIBC
287291
returns (address, string memory)
288292
{
293+
if (!_equal(msg_.portId.toShortString(), port)) {
294+
revert ICS20UnexpectedPort(msg_.portId, port.toString());
295+
}
289296
if (msg_.order != Channel.Order.ORDER_UNORDERED) {
290297
revert IBCModuleChannelOrderNotAllowed(msg_.portId, msg_.channelId, msg_.order);
291298
}
@@ -423,4 +430,11 @@ contract ICS20Transfer is IBCAppBase, IICS20Errors {
423430
function _decodeReceiver(string memory receiver) internal pure virtual returns (address, bool) {
424431
return ICS20Lib.hexStringToAddress(receiver);
425432
}
433+
434+
/**
435+
* @dev _equal compares two ShortString values.
436+
*/
437+
function _equal(ShortString a, ShortString b) internal pure returns (bool) {
438+
return ShortString.unwrap(a) == ShortString.unwrap(b);
439+
}
426440
}

contracts/apps/20-transfer/IICS20Errors.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ pragma solidity ^0.8.20;
44
interface IICS20Errors {
55
/// @param version Version string
66
error ICS20UnexpectedVersion(string version);
7+
/// @param actual port
8+
/// @param expected port
9+
error ICS20UnexpectedPort(string actual, string expected);
710
/// @param tokenContract Address of the token contract
811
error ICS20InvalidTokenContract(address tokenContract);
912
/// @param tokenContract Address of the token contract

0 commit comments

Comments
 (0)