Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ICS20 App on IBC #793

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
45e890d
initial commits
sdpisreddevil Nov 28, 2023
4b606e3
add ICS20Bank contract
sdpisreddevil Dec 12, 2023
c790b28
add ICS20Bank contract
sdpisreddevil Dec 12, 2023
bd9181f
add ICS20Lib contract
sdpisreddevil Dec 12, 2023
c074ebe
add ICS20TransferBank contract
sdpisreddevil Dec 12, 2023
750fa1e
add ICS20Transfer contract
sdpisreddevil Dec 12, 2023
c7789bd
fix comments on ICS20Bank
sdpisreddevil Dec 19, 2023
288e857
fix comments on ICS20Bank
sdpisreddevil Dec 19, 2023
a24d0ab
fix comments on ICS20Bank
sdpisreddevil Dec 20, 2023
4674725
fix comments on ICS20TransferBank on sendTransfer
sdpisreddevil Dec 20, 2023
6b0cfa0
chore remove whitespaces, unused imports
sdpisreddevil Dec 26, 2023
bec141a
add refund logic
sdpisreddevil Dec 26, 2023
31e14c1
fix: IIBCModule with added struct of messages
sdpisreddevil Dec 26, 2023
2a442e2
fix: build issues
sdpisreddevil Dec 26, 2023
1491efc
update: Struct on IIBCModule on msgTypes
sdpisreddevil Dec 26, 2023
099b387
fix: seperate ics20 contracts
viveksharmapoudel Dec 26, 2023
f6e7615
update: build.gradle
Dec 27, 2023
cc56743
remove: unused ibcbaseApp
Dec 27, 2023
a7ac65d
fix: setupRole on ics20Bank with primitive int inplace of Integer
Dec 27, 2023
1e40a29
fix: use of arrays.slice on ICS20Transfer
Dec 27, 2023
2668cc4
fix: set Address of IBChandler and bank
Dec 27, 2023
e35e841
fix: add onChanCloseConfirm and onchanOpenConfirm on ICS20Transfer
Dec 27, 2023
10955ff
fix: REMOVE the struct from IIBCModule
Dec 27, 2023
45ef0dd
fix: vardb message
Dec 27, 2023
0ccbb1b
fix: ics20App build.gradle with mainClassName to ICS20TransferBank
sdpisreddevil Dec 28, 2023
89e57ce
fix: remove unwanted params
sdpisreddevil Dec 28, 2023
1b7622a
fix: remove unused variables
sdpisreddevil Dec 28, 2023
33f9515
fix: getIBCAddress from parent class
sdpisreddevil Dec 28, 2023
ee328eb
fix: deploy of the contract
sdpisreddevil Dec 28, 2023
78aa099
update: add destinationPort and destinationChannel on ICS20Transfer o…
sdpisreddevil Dec 28, 2023
427b14b
fix: sendTransfer with packetMarshal data
sdpisreddevil Dec 28, 2023
75a89f5
fix: version check on onchanOpenTry
sdpisreddevil Dec 28, 2023
df44436
add: string split method on StringUtil for unmarshalJson
sdpisreddevil Dec 28, 2023
ad67542
add: onlyIBCHandler check on public methods
sdpisreddevil Dec 28, 2023
447f3ce
remove unused imports
sdpisreddevil Dec 28, 2023
aa659bc
fix:
sdpisreddevil Dec 28, 2023
6c96e08
update:
sdpisreddevil Dec 28, 2023
611c8c8
fix: unmarshal Json with better way to handle possible human issue on…
sdpisreddevil Dec 28, 2023
4dc2a46
fix: json unmarshal ics20 packet remove specific character
viveksharmapoudel Dec 29, 2023
c0a685f
add:
sdpisreddevil Dec 29, 2023
aa8ac65
add:
sdpisreddevil Dec 29, 2023
f8a1ec3
add:
sdpisreddevil Dec 29, 2023
4b52e25
update:
sdpisreddevil Dec 29, 2023
67d3212
update:
sdpisreddevil Dec 29, 2023
fe28512
fix: ics20 transfer issue
viveksharmapoudel Jan 3, 2024
9a1e784
fix:ics20Bank constructor for contract upgrade case and add todo for …
sdpisreddevil Jan 3, 2024
7703072
fix:
sdpisreddevil Jan 3, 2024
f6f40e5
fix:
sdpisreddevil Jan 3, 2024
505f952
chore: remove unused methods
sdpisreddevil Jan 3, 2024
ce504d9
chore: remove unused imports
sdpisreddevil Jan 3, 2024
c287d3c
fix: add getRole to get user role on ICS20Bank
sdpisreddevil Jan 4, 2024
17c84bb
fix: add contract name
sdpisreddevil Jan 4, 2024
0594a7b
fix: add contract name
sdpisreddevil Jan 4, 2024
60a0dff
add test cases for ics20Bank - WIP
sdpisreddevil Jan 4, 2024
c633bb3
add test cases for ics20app - WIP
sdpisreddevil Jan 4, 2024
e40fbb2
fix: comment failing test cases for ics20app - WIP
sdpisreddevil Jan 4, 2024
a9f18e0
fix: Add icx transfer case from app
sdpisreddevil Jan 8, 2024
a629251
fix: Add icx test case from app
sdpisreddevil Jan 8, 2024
301681c
fix: withdraw to be done by only caller wallet
sdpisreddevil Feb 12, 2024
0dc813c
fix: add interface details on each method
sdpisreddevil Feb 12, 2024
cbd799b
fix: replace `==` by equals for `byte`
sdpisreddevil Feb 12, 2024
7da48fd
fix: remove params on gradle
sdpisreddevil Feb 12, 2024
476d79b
Merge branch 'main' into feature/add-ics20-transfer-app
sdpisreddevil Feb 12, 2024
8e7ee1c
Merge pull request #2 from sdpisreddevil/fix/build-separate-ics20-tra…
sdpisreddevil Feb 12, 2024
5c97bc7
fix: remove temp variables and methods
sdpisreddevil Feb 12, 2024
7237b0b
fix: use TAG Variable for revert message
sdpisreddevil Feb 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions contracts/javascore/ibc/src/main/java/ibc/ics20/app/ICS20Bank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ibc.ics20.app;

import ibc.ics24.host.IBCCommitment;
import score.Address;
import score.BranchDB;
import score.Context;
import score.DictDB;
import score.annotation.External;

import java.math.BigInteger;

public class ICS20Bank {

private static final byte[] ADMIN_ROLE = IBCCommitment.keccak256("ADMIN_ROLE".getBytes());
private static final byte[] OPERATOR_ROLE = IBCCommitment.keccak256("OPERATOR_ROLE".getBytes());

private static final Integer ADMIN_ROLE_ID = 1;
private static final Integer OPERATOR_ROLE_ID = 2;


// Mapping from token ID to account balances
private final BranchDB<String, DictDB<Address, BigInteger>> balances = Context.newBranchDB("BALANCES", BigInteger.class);
private final DictDB<Address, Integer> roles = Context.newDictDB("ROLES", Integer.class);


public ICS20Bank() {
setupRole(ADMIN_ROLE_ID, Context.getCaller());
}

@External
public void setupRole(Integer role, Address account) {
Context.require(Context.getCaller().equals(Context.getOwner()), "Only owner can set up role");
roles.set(account, role);
}

@External
public void setupOperator(Address account) {
setupRole(OPERATOR_ROLE_ID, account);
}

private boolean hasRole(Integer role, Address account) {
return (role == roles.getOrDefault(account, 0));
}

@External(readonly = true)
public BigInteger balanceOf(Address account, String denom) {
Context.require(account != ICS20Transfer.ZERO_ADDRESS, "ICS20Bank: balance query for the zero address");

// Assuming the denomination is a valid key in the balances mapping
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
return balances.at(denom).getOrDefault(account, BigInteger.ZERO);
}

@External
public void transferFrom(Address from, Address to, String denom, BigInteger amount) {
Context.require(to != ICS20Transfer.ZERO_ADDRESS, "ICS20Bank: balance query for the zero address");
sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
Address caller = Context.getCaller();
Context.require(from.equals(caller) || hasRole(OPERATOR_ROLE_ID, caller), "ICS20Bank: caller is not owner nor approved");
BigInteger fromBalance = balanceOf(from, denom);
Context.require(amount.compareTo(BigInteger.ZERO) > 0, "ICS20Bank: transfer amount must be greater than zero");
Context.require(fromBalance.compareTo(amount) >= 0, "ICS20Bank: insufficient balance for transfer");

balances.at(denom).set(from, fromBalance.subtract(amount));
balances.at(denom).set(to, balanceOf(to, denom).add(amount));
}

@External
public void mint(Address account, String denom, BigInteger amount) {
Context.require(hasRole(OPERATOR_ROLE_ID, Context.getCaller()), "ICS20Bank: must have minter role to mint");
Context.require(account != ICS20Transfer.ZERO_ADDRESS, "ICS20Bank: mint to the zero address");
Context.require(amount.compareTo(BigInteger.ZERO) > 0, "ICS20Bank: mint amount must be greater than zero");
_mint(account, denom, amount);
}

@External
public void burn(Address account, String denom, BigInteger amount) {
Context.require(hasRole(OPERATOR_ROLE_ID, Context.getCaller()), "ICS20Bank: must have minter role to mint");
Context.require(amount.compareTo(BigInteger.ZERO) > 0, "ICS20Bank: mint amount must be greater than zero");
sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
_burn(account, denom, amount);
}

@External
public void deposit(Address tokenContract, BigInteger amount, Address receiver){
Context.require(tokenContract.isContract(), "ICS20Bank: tokenContract is not a contract");
Context.call(tokenContract, "transferFrom", Context.getCaller(), Context.getAddress(), amount);
sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
_mint(receiver, tokenContract.toString(), amount);
}

@External
public void withdraw(Address tokenContract, BigInteger amount, Address receiver){
Context.require(tokenContract.isContract(), "ICS20Bank: tokenContract is not a contract");
_burn(receiver, tokenContract.toString(), amount);
Context.call(tokenContract, "transfer", receiver, amount);
}

private void _mint(Address account, String denom, BigInteger amount){
balances.at(denom).set(account, balanceOf(account, denom).add(amount));
}

private void _burn(Address account, String denom, BigInteger amount){
BigInteger accountBalance = balanceOf(account, denom);
sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
Context.require(accountBalance.compareTo(amount) >= 0, "ICS20Bank: burn amount exceeds balance");
balances.at(denom).set(account, accountBalance.subtract(amount));
}
}
84 changes: 84 additions & 0 deletions contracts/javascore/ibc/src/main/java/ibc/ics20/app/ICS20Lib.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ibc.ics20.app;

import score.Address;
import score.Context;

import java.math.BigInteger;

public class ICS20Lib {

public static class PacketData {
public String denom;
public String sender;
public String receiver;
public BigInteger amount;
public String memo;
}


public static final byte[] SUCCESSFUL_ACKNOWLEDGEMENT_JSON = "{\"result\":\"AQ==\"}".getBytes();
public static final byte[] FAILED_ACKNOWLEDGEMENT_JSON = "{\"error\":\"failed\"}".getBytes();
// bytes32 public constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256(SUCCESSFUL_ACKNOWLEDGEMENT_JSON);

// public static final Integer CHAR_DOUBLE_QUOTE = 0x22;
public static final Integer CHAR_SLASH = 0x2f;
public static final Integer CHAR_BACKSLASH = 0x5c;
public static final Integer CHAR_F = 0x66;
public static final Integer CHAR_R = 0x72;
public static final Integer CHAR_N = 0x6e;
public static final Integer CHAR_B = 0x62;
public static final Integer CHAR_T = 0x74;
public static final Integer CHAR_CLOSING_BRACE = 0x7d;
public static final Integer CHAR_M = 0x6d;
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();

private static final int CHAR_DOUBLE_QUOTE = '"';

// Function to check if escape is needed for a byte array in Java
static boolean isEscapeNeededString(byte[] bz) {
for (int i = 0; i < bz.length; i++) {
int c = bz[i] & 0xFF; // Convert to unsigned int
if (c == CHAR_DOUBLE_QUOTE) {
return true;
}
}
return false;
}



public static byte[] marshalJson(String escapedDenom, BigInteger amount, String escapedSender, String escapedReceiver) {
String jsonString = "{\"amount\":\"" +
amount.toString() +
"\",\"denom\":\"" +
escapedDenom +
"\",\"receiver\":\"" +
escapedReceiver +
"\",\"sender\":\"" +
escapedSender +
"\"}";

return jsonString.getBytes();
}

static String addressToHexString(String addr) {
StringBuilder hexString = new StringBuilder("0x");
long localValue = Long.parseLong(addr.substring(2), 16);

for (int i = 39; i >= 0; --i) {
hexString.append(HEX_DIGITS[(int) (localValue & 0xf)]);
localValue >>= 4;
}

if (localValue != 0) {
Context.revert("Insufficient hex length");
}

return hexString.toString();
}





}
179 changes: 179 additions & 0 deletions contracts/javascore/ibc/src/main/java/ibc/ics20/app/ICS20Transfer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package ibc.ics20.app;

import ibc.icon.interfaces.IIBCModule;
import ibc.icon.score.util.Logger;
import ibc.icon.score.util.StringUtil;
import ibc.ics05.port.ModuleManager;
import ibc.ics25.handler.IBCHandler;

import icon.proto.core.channel.Channel;
import icon.proto.core.channel.Packet;
import score.Address;
import score.Context;
import score.DictDB;
import score.annotation.External;

import java.math.BigInteger;
import java.util.Map;

public class ICS20Transfer {
public static String ICS20_VERSION = "ics20-1";
public static Address ZERO_ADDRESS = Address.fromString("hx0000000000000000000000000000000000000000");


public static final DictDB<String, Address> channelEscrowAddresses = Context.newDictDB("channelEscrowAddresses", Address.class);


@External
public byte[] onRecvPacket(byte[] packet, Address relayer) {
// TODO unmarshal json
boolean success = false;
sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
Address receiver = ZERO_ADDRESS;
receiver, success = _decodeReceiver(packet.data.receiver);
if (!success) {
return ICS20Lib.FAILED_ACKNOWLEDGEMENT_JSON;
}

byte[] denomPrefix = getDenomPrefix(packet.data.sourcePort, packet.data.sourceChannel);
byte[] denom = packet.data.denom.getBytes();
if (denom.length >= denomPrefix.length) {
// denom slicing todo
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldnot length of denom be always greater than denomPrefix? .i think we would need to compare denom prefix with packet sourcePort and sourceChannel to determine if sender is source or sink then burn token if sender is source.

success = _transferFrom(getEscrowAddress(packet.data.sourceChannel), receiver, packet.data.denom, packet.data.amount);
} else{
if(ICS20Lib.isEscapeNeededString(denom)){
success = false;
}else{
success = _mint(receiver, String(getDenomPrefix(packet.destination_port, packet.destination_channel), denom), packet.data.amount);
}
}

if (success) {
return ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON;
}else{
return ICS20Lib.FAILED_ACKNOWLEDGEMENT_JSON;
}


return packet;
}


@External
public void onAcknowledgementPacket(byte[] calldata, byte[] acknowledgement, Address relayer) {
Context.println("onAcknowledgementPacket");
// Context.require(Context.getCaller().equals(getIBCAddress()), "caller is not handler");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can decode packet from calldata ... and lets rename it to packetBytes as well.

if (acknowledgement != ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON) {
refundTokens(calldata, packet.source_port, packet.source_channel);
}

}

@External
public String onChanOpenInit(IIBCModule.onChanOpenInit msg) {
// srcChan.set(channelId);
// srcPort.set(portId);
// Channel.Counterparty counterparty = Channel.Counterparty.decode(counterpartyPb);
// dstPort.set(counterparty.getPortId());
// Context.println("onChanOpenInit");
return ICS20_VERSION;
}

@External
public String onChanOpenTry(IIBCModule.onChanOpenTry msg) {
return ICS20_VERSION;
}

@External
public void onChanOpenAck(IIBCModule.MsgOnChanOpenAck msg) {

}

@External
public void onChanCloseInit(IIBCModule.MsgOnChanCloseInit msg) {
Context.revert("Not Allowed");
}

@External
public void onTimeoutPacket(Packet packet, byte[] proofHeight, byte[] proof, BigInteger nextSequenceRecv) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to refund on packet timeout as well.

}


static Address getEscrowAddress(String sourceChannel) {
Address escorw = channelEscrowAddresses.get(sourceChannel);
Context.require(escorw != ZERO_ADDRESS);
return escorw;
}

private void refundTokens(ICS20Lib.PacketData data, String sourcePort, String sourceChannel) {
byte[] denomPrefix = getDenomPrefix(sourcePort, sourceChannel);
byte[] denom = data.denom.getBytes();

if (denom.length >= denomPrefix.length) {

sdpisreddevil marked this conversation as resolved.
Show resolved Hide resolved
Context.println("if");
} else {
Context.println("else");
}


}

public static byte[] getDenomPrefix(String port, String channel) {
return StringUtil.encodePacked(port, "/", channel, "/");
}

protected boolean _transferFrom(Address sender, Address receiver, String denom, BigInteger amount) {
// Implementation goes here
// Return true if minting is successful, false otherwise
return true;
}

protected boolean _mint(Address account, String denom, BigInteger amount) {
// Implementation goes here
// Return true if minting is successful, false otherwise
return true;
}

/**
* @dev _burn burns tokens from `account` in the bank.
*/
protected boolean _burn(Address account, String denom, BigInteger amount) {
// Implementation goes here
// Return true if burning is successful, false otherwise
return true;
}

/**
* @dev _encodeSender encodes an address to a hex string.
* The encoded sender is used as `sender` field in the packet data.
*/
protected static String _encodeSender(Address sender) {
return ICS20Lib.addressToHexString(sender.toString());
}


protected Address _decodeSender(String sender) {
// Map<Address, Boolean> result = ICS20Lib.hexStringToAddress(sender);
// require(result.getSecond(), "invalid address");
// return result.getFirst();
return (ZERO_ADDRESS);
}

/**
* @dev _decodeReceiver decodes a hex string to an address.
* `receiver` may be an invalid address format.
*/
protected Map<Address, Boolean> _decodeReceiver(String receiver) {
boolean flag;
try {
Address.fromString(receiver);
flag = true;
} catch (Exception e) {
flag = false;

}
return Map.of(Address.fromString(receiver), flag);
}


}
Loading
Loading