Skip to content

Commit 4af1765

Browse files
ibrizsabinizyak
andauthored
docs: add ics-20 deviation (#841)
* docs: add ics-20 deviation * fix: remove transfer from * updates on ICS20 specs * chore: typo * docs: separate function for sendICX and save tokenAddress --------- Co-authored-by: izyak <lbackup887@gmail.com>
1 parent 9d25c2e commit 4af1765

File tree

1 file changed

+239
-0
lines changed

1 file changed

+239
-0
lines changed

docs/adr/ICS-20_Deviation.md

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
## Introduction
2+
This document outlines the ICS-20 workflow that supports IRC2 Tokens instead of a consolidated Bank Module. This will help us to represent transferred tokens in proper IRC2 wrapped assets.
3+
This is an extension of [ICS20](https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md) specs to support IBC on ICON.
4+
5+
### Logical Components
6+
7+
#### Token Contract:
8+
This will be mintabe burnable IRC2 token contract that will be deployed for each local asset or foreign wrapped asset. We will be using the audited [IRC2 Tradeable Contract](https://github.com/icon-project/icon-bridge/tree/main/javascore/irc2Tradeable) from the ICON Bridge repo
9+
10+
#### ICS20 Contract:
11+
This contract will be entrypoint for sending and receiving tokens from foreign chains. It will also maintain a registry of tokens that are allowed to be transferred to and fro. Official ICS-20 specs can be found [here](https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md).
12+
13+
Any arbitrary tokens from the COSMOS chains will not be allowed to transfer to ICON. Only registered tokens will be allowed to do so. The ICS20 Contract will have an admin, which can register tokens of COSMOS chains on ICON. Once registered, those tokens can be minted on ICON by the ICS20 contract. The name of the token MUST be the corresponding denom from centauri chain.
14+
15+
**Cross chain tokens to be supported**
16+
17+
If `ARCH` token is to be bridged from Archway to ICON, it has to go through Archway -> Centauri -> Icon.
18+
19+
Denom of ARCH on centauri: `transfer/channel-X/ARCH`
20+
21+
Denom of ARCH on icon: `transfer/channel-ICON/transfer/channel-X/ARCH`.
22+
23+
The name of the token to register MUST be `transfer/channel-ICON/transfer/channel-X/ARCH`.
24+
25+
If ARCH token was transfered to another cosmos chain, neutron, and if that needs to be sent to ICON, it MUST go back to Archway chain, then be sent to ICON via centauri. Only the denoms from their native chain will be supported.
26+
27+
#### Token register
28+
```js
29+
function registerCosmosToken(name: String, symbol: String, decimals: int) {
30+
onlyAdmin()
31+
tokenAddress = deployIRC2Tradeable(name, symbol, decimals)
32+
tokenContracts[name] = tokenAddress
33+
}
34+
```
35+
36+
The following function will be used to register tokens on ICON to the ICS 20 App.
37+
38+
```js
39+
function registerIconToken(tokenAddress: Address) {
40+
onlyAdmin()
41+
tokenContracts[tokenAddress.toString()] = tokenAddress
42+
}
43+
```
44+
45+
#### Helper methods
46+
```js
47+
function isNativeAsset(denom:String){
48+
return denom=="icx"
49+
}
50+
51+
function getTokenContractAddress(denom:String):String {
52+
assert(tokenContracts[denom]!=null)
53+
return tokenContracts[denom]
54+
}
55+
```
56+
57+
#### Sending Tokens
58+
- To send ICX, send using the `sendICX` function.
59+
60+
- To send tokens other then ICX, it should go through `tokenFallback` function. The `data` bytes, should be parsed into the following structure.
61+
```json
62+
{
63+
"method": "sendFungibleTokens",
64+
"params": {
65+
"denomination": "string",
66+
"amount": "uint64",
67+
"sender": "string",
68+
"receiver": "string",
69+
"sourcePort": "string",
70+
"sourceChannel": "string",
71+
"timeoutHeight": {
72+
"latestHeight": "uint64",
73+
"revisionNumber": "uint64",
74+
},
75+
"timeoutTimestamp": "uint64",
76+
"memo":"string"
77+
}
78+
}
79+
```
80+
- Implementation of `tokenFallback` and `sendICX` function
81+
```js
82+
// to send tokens other than icx
83+
function tokenFallback(from: Address, value: uint64, data: bytes) {
84+
data = parseStructure(data)
85+
if data.method == "sendFungibleTokens" {
86+
sendFungibleToken = parseFungibleToken(data.params)
87+
assert(sendFungibleToken.amount == value)
88+
assert(sendFungibleToken.sender == from)
89+
sendFungibleTokens(...)
90+
} else {
91+
revert("wrong data")
92+
}
93+
}
94+
95+
@payable
96+
function sendICX(
97+
receiver: string,
98+
sourcePort: string,
99+
sourceChannel: string,
100+
timeoutHeight: Height,
101+
timeoutTimestamp: string,
102+
@Optional memo: string
103+
) {
104+
sendFungibleTokens("icx", Context.getValue(), Context.getCaller().toString(), ...)
105+
}
106+
107+
108+
internal function sendFungibleTokens(
109+
denomination: string,
110+
amount: uint256,
111+
sender: string,
112+
receiver: string,
113+
sourcePort: string,
114+
sourceChannel: string,
115+
timeoutHeight: Height,
116+
timeoutTimestamp: uint64, // in unix nanoseconds
117+
@Optional memo: string
118+
): uint64 {
119+
prefix = "{sourcePort}/{sourceChannel}/"
120+
// we are the source if the denomination is not prefixed
121+
source = denomination.slice(0, len(prefix)) !== prefix
122+
tokenContract=getTokenContracts(denomination)
123+
if source {
124+
if isNativeAsset(denomination) {
125+
assert amount == Context.getValue()
126+
}
127+
}
128+
if !source {
129+
tokenContract.burn(amount);
130+
}
131+
132+
// create FungibleTokenPacket data
133+
data = FungibleTokenPacketData{denomination, amount, sender, receiver, memo}
134+
135+
// send packet using the interface defined in ICS4
136+
sequence = handler.sendPacket(
137+
getCapability("port"),
138+
sourcePort,
139+
sourceChannel,
140+
timeoutHeight,
141+
timeoutTimestamp,
142+
json.marshal(data) // json-marshalled bytes of packet data
143+
)
144+
145+
return sequence
146+
}
147+
148+
```
149+
150+
#### Receiving tokens
151+
152+
```js
153+
154+
function onRecvPacket(packet: Packet) {
155+
FungibleTokenPacketData data = packet.data
156+
assert(data.denom !== "")
157+
assert(data.amount > 0)
158+
assert(data.sender !== "")
159+
assert(data.receiver !== "")
160+
161+
// construct default acknowledgement of success
162+
FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null}
163+
prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
164+
// we are the source if the packets were prefixed by the sending chain
165+
source = data.denom.slice(0, len(prefix)) === prefix
166+
assert data.receiver is Address
167+
if source {
168+
// receiver is source chain: unescrow tokens
169+
// determine escrow account
170+
denomOnly=data.denom.slice(len(prefix),len(data.denom.prefix));
171+
if isNativeAsset(denomOnly){
172+
Context.transfer(data.receiver, data.amount)
173+
}
174+
tokenContract=getTokenContract(denomOnly)
175+
// unescrow tokens to receiver (assumed to fail if balance insufficient)
176+
try {
177+
tokenContract.transfer(data.receiver,data.amount)
178+
} catch (Exception e) {
179+
ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
180+
}
181+
} else {
182+
prefix = "{packet.destPort}/{packet.destChannel}/"
183+
prefixedDenomination = prefix + data.denom
184+
tokenContract=getTokenContract(prefixedDenomination)
185+
try {
186+
// sender was source, mint vouchers to receiver (assumed to fail if balance insufficient)
187+
tokenContract.mint(data.receiver, data.amount)
188+
} catch (Exception e) {
189+
ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
190+
}
191+
}
192+
return ack
193+
}
194+
```
195+
196+
#### Acknowledge Packet
197+
```js
198+
function onAcknowledgePacket(
199+
packet: Packet,
200+
acknowledgement: bytes) {
201+
// if the transfer failed on dst chain, refund the tokens
202+
if (!acknowledgement.success)
203+
refundTokens(packet)
204+
}
205+
```
206+
#### Timeout Packet
207+
```js
208+
function onTimeoutPacket(packet: Packet) {
209+
// the packet timed-out, so refund the tokens
210+
refundTokens(packet)
211+
}
212+
```
213+
#### Refund logic
214+
```js
215+
216+
function refundTokens(packet: Packet) {
217+
FungibleTokenPacketData data = packet.data
218+
prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
219+
// we are the source if the denomination is not prefixed
220+
tokenContract=getTokenContracts(data.denom)
221+
source = data.denom.slice(0, len(prefix)) !== prefix
222+
if source {
223+
// sender was source chain, unescrow tokens back to sender
224+
if isNativeAsset {
225+
Context.transfer(data.sender, data.amount)
226+
return
227+
}
228+
tokenContract.transfer(data.sender, data.amount)
229+
} else {
230+
// receiver was source chain, mint vouchers back to sender
231+
tokenContract.mint(data.sender,data.amount)
232+
}
233+
}
234+
235+
```
236+
237+
### Hopchain
238+
Hop is done based on the memo field during sendFungibleTokens. The structure of memo should follow the following [spec](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware)
239+

0 commit comments

Comments
 (0)