diff --git a/javascore/nativecoinIRC2/build.gradle b/javascore/nativecoinIRC2/build.gradle
new file mode 100644
index 00000000..c193c7f7
--- /dev/null
+++ b/javascore/nativecoinIRC2/build.gradle
@@ -0,0 +1,68 @@
+version = '0.1.0'
+
+dependencies {
+ compileOnly 'foundation.icon:javaee-api:0.9.0'
+ compileOnly 'foundation.icon:javaee-rt:0.9.0'
+ compileOnly 'foundation.icon:icon-sdk:2.0.0'
+ compileOnly 'foundation.icon:javaee-tooling:0.8.7'
+
+ implementation project(':score-util')
+ implementation project(':lib')
+ implementation 'com.github.sink772:javaee-scorex:0.5.2'
+ implementation 'com.github.sink772:javaee-tokens:0.5.5'
+ implementation 'org.msgpack:msgpack-core:0.8.17'
+
+ testImplementation 'foundation.icon:javaee-api:0.9.0'
+ testImplementation 'foundation.icon:javaee-rt:0.9.0'
+ testImplementation 'foundation.icon:icon-sdk:2.0.0'
+ testImplementation 'foundation.icon:javaee-tooling:0.9.0'
+ testImplementation 'org.mockito:mockito-core:3.3.3'
+ testImplementation 'com.squareup.okhttp3:okhttp:3.11.0'
+ testImplementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
+ testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60'
+
+ testImplementation fileTree(dir: './lib', include: 'goloop-testsuite.jar')
+ testImplementation fileTree(dir: './lib', include: 'testsvc.jar')
+
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
+}
+
+optimizedJar {
+ mainClassName = 'foundation.icon.btp.bsh.NativeCoinService'
+ archivesBaseName = 'native-bsh'
+ from {
+ configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+ }
+}
+logging.println("BMC Address :" + System.getProperty("BMC_ADDRESS"))
+deployJar {
+ endpoints {
+ gangnam {
+ uri = 'https://gicon.net.solidwallet.io/api/v3'
+ nid = 7
+ }
+ Sejong {
+ uri = 'https://sejong.net.solidwallet.io/api/v3'
+ nid = 0x53
+ }
+ local {
+ uri = 'http://localhost:9082/api/v3'
+ nid = 3
+ }
+ BTPTestnet {
+ uri = 'https://btp.net.solidwallet.io/api/v3'
+ nid = 0x42
+ }
+ }
+ keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : ''
+ password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : ''
+
+ parameters {
+ arg('_bmc', System.getProperty("BMC_ADDRESS"))
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/javascore/nativecoinIRC2/lib/goloop-testsuite.jar b/javascore/nativecoinIRC2/lib/goloop-testsuite.jar
new file mode 100644
index 00000000..931df1cb
Binary files /dev/null and b/javascore/nativecoinIRC2/lib/goloop-testsuite.jar differ
diff --git a/javascore/nativecoinIRC2/lib/testsvc.jar b/javascore/nativecoinIRC2/lib/testsvc.jar
new file mode 100644
index 00000000..885a76cf
Binary files /dev/null and b/javascore/nativecoinIRC2/lib/testsvc.jar differ
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Asset.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Asset.java
new file mode 100644
index 00000000..4694f54f
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Asset.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+
+import java.math.BigInteger;
+
+public class Asset {
+ private String coinName;
+ private BigInteger amount;
+
+ public Asset() {}
+
+ public Asset(Asset asset) {
+ setCoinName(asset.getCoinName());
+ setAmount(asset.getAmount());
+ }
+
+ public Asset(String coinName, BigInteger amount) {
+ this.coinName = coinName;
+ this.amount = amount;
+ }
+
+ public String getCoinName() {
+ return coinName;
+ }
+
+ public void setCoinName(String coinName) {
+ this.coinName = coinName;
+ }
+
+ public BigInteger getAmount() {
+ return amount;
+ }
+
+ public void setAmount(BigInteger amount) {
+ this.amount = amount;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Asset{");
+ sb.append("coinName='").append(coinName).append('\'');
+ sb.append(", amount=").append(amount);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, Asset obj) {
+ obj.writeObject(writer);
+ }
+
+ public static Asset readObject(ObjectReader reader) {
+ Asset obj = new Asset();
+ reader.beginList();
+ obj.setCoinName(reader.readNullable(String.class));
+ obj.setAmount(reader.readNullable(BigInteger.class));
+ reader.end();
+ return obj;
+ }
+
+
+ public void writeObject(ObjectWriter writer) {
+ writer.beginList(2);
+ writer.writeNullable(this.getCoinName());
+ writer.writeNullable(this.getAmount());
+ writer.end();
+ }
+
+ public static Asset fromBytes(byte[] bytes) {
+ ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes);
+ return Asset.readObject(reader);
+ }
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ Asset.writeObject(writer, this);
+ return writer.toByteArray();
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/AssetTransferDetail.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/AssetTransferDetail.java
new file mode 100644
index 00000000..b2373081
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/AssetTransferDetail.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+
+import java.math.BigInteger;
+
+public class AssetTransferDetail extends Asset {
+ private BigInteger fee;
+
+ public BigInteger getFee() {
+ return fee;
+ }
+
+ public void setFee(BigInteger fee) {
+ this.fee = fee;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("AssetTransferDetail{");
+ sb.append("fee=").append(fee);
+ sb.append('}').append(super.toString());
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, AssetTransferDetail obj) {
+ obj.writeObject(writer);
+ }
+
+ public static AssetTransferDetail readObject(ObjectReader reader) {
+ AssetTransferDetail obj = new AssetTransferDetail();
+ reader.beginList();
+ obj.setCoinName(reader.readNullable(String.class));
+ obj.setAmount(reader.readNullable(BigInteger.class));
+ obj.setFee(reader.readNullable(BigInteger.class));
+ reader.end();
+ return obj;
+ }
+
+ public void writeObject(ObjectWriter writer) {
+ writer.beginList(3);
+ writer.writeNullable(this.getCoinName());
+ writer.writeNullable(this.getAmount());
+ writer.writeNullable(this.getFee());
+ writer.end();
+ }
+
+ public static AssetTransferDetail fromBytes(byte[] bytes) {
+ ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes);
+ return AssetTransferDetail.readObject(reader);
+ }
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ AssetTransferDetail.writeObject(writer, this);
+ return writer.toByteArray();
+ }
+
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMC.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMC.java
new file mode 100644
index 00000000..1b094d27
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMC.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.Address;
+import score.annotation.External;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+public interface BMC {
+ /**
+ * Registers BMV for the network.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _net String (Network Address of the blockchain )
+ * @param _addr Address (the address of BMV)
+ */
+ @External
+ void addVerifier(String _net, Address _addr);
+
+ /**
+ * Unregisters BMV for the network.
+ * May fail if it's referred by the link.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _net String (Network Address of the blockchain )
+ */
+ @External
+ void removeVerifier(String _net);
+
+ /**
+ * Get registered verifiers.
+ *
+ * @return A dictionary with the Network Address as a key and smart contract address of the BMV as a value.
+ *
+ * For Example::
+ *
+ * {
+ * "0x1.iconee": "cx72eaed466599ca5ea377637c6fa2c5c0978537da"
+ * }
+ */
+ @External(readonly = true)
+ Map getVerifiers();
+
+ /**
+ * Registers the smart contract for the service.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _svc String (the name of the service)
+ * @param _addr Address (the address of the smart contract handling the service)
+ */
+ @External
+ void addService(String _svc, Address _addr);
+
+ /**
+ * Unregisters the smart contract for the service.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _svc String (the name of the service)
+ */
+ @External
+ void removeService(String _svc);
+ /**
+ * Get registered services.
+ *
+ * @return A dictionary with the name of the service as key and address of the BSH related to the service as value.
+ *
For example::
+ * {
+ * "token": "cx72eaed466599ca5ea377637c6fa2c5c0978537da"
+ * }
+ */
+ @External(readonly = true)
+ Map getServices();
+
+ /**
+ * If it generates the event related to the link, the relay shall handle the event to deliver BTP Message to the BMC.
+ * If the link is already registered, or its network is already registered then it fails.
+ * If there is no verifier related with the network of the link, then it fails.
+ * Initializes status information for the link.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _link String (BTP Address of connected BMC)
+ */
+ @External
+ void addLink(String _link);
+
+ /**
+ * Removes the link and status information.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _link String (BTP Address of connected BMC)
+ */
+ @External
+ void removeLink(String _link);
+
+ /**
+ * Get status of BMC.
+ * Used by the relay to resolve next BTP Message to send.
+ * If target is not registered, it will fail.
+ *
+ * @param _link String ( BTP Address of the connected BMC )
+ * @return The object contains followings fields.
+ */
+ @External(readonly = true)
+ BMCStatus getStatus(String _link);
+
+ /**
+ * Get registered links.
+ *
+ * @return A list of links ( BTP Addresses of the BMCs )
+ *
For Example::
+ * [
+ * "btp://0x1.iconee/cx9f8a75111fd611710702e76440ba9adaffef8656"
+ * ]
+ */
+ @External(readonly = true)
+ String[] getLinks();
+
+ /**
+ * Add route to the BMC.
+ * May fail if there more than one BMC for the network.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _dst String ( BTP Address of the destination BMC )
+ * @param _link String ( BTP Address of the next BMC for the destination )
+ */
+ @External
+ void addRoute(String _dst, String _link);
+ /**
+ * Remove route to the BMC.
+ * Called by the operator to manage the BTP network.
+ *
+ * @param _dst String ( BTP Address of the destination BMC )
+ */
+ @External
+ void removeRoute(String _dst);
+ /**
+ * Get routing information.
+ *
+ * @return A dictionary with the BTP Address of the destination BMC as key and the BTP Address of the next as value.
+ *
+ *
For Example::
+ * {
+ * "btp://0x2.iconee/cx1d6e4decae8160386f4ecbfc7e97a1bc5f74d35b": "btp://0x1.iconee/cx9f8a75111fd611710702e76440ba9adaffef8656"
+ * }
+ */
+ @External(readonly = true)
+ Map getRoutes();
+
+ /**
+ * Sends the message to a specific network.
+ * Only allowed to be called by registered BSHs.
+ *
+ * @param _to String ( Network Address of destination network )
+ * @param _svc String ( name of the service )
+ * @param _sn Integer ( serial number of the message, must be positive )
+ * @param _msg Bytes ( serialized bytes of Service Message )
+ */
+ @External
+ void sendMessage(String _to, String _svc, BigInteger _sn, byte[] _msg);
+
+ /**
+ * It verifies and decodes the Relay Message with BMV and dispatches BTP Messages to registered BSHs.
+ * It's allowed to be called by registered Relay.
+ *
+ * @param _prev String ( BTP Address of the previous BMC )
+ * @param _msg String ( base64 encoded string of serialized bytes of Relay Message )
+ */
+ @External
+ void handleRelayMessage(String _prev, String _msg);
+
+ /**
+ * TODO [TBD] add 'getBtpAddress' to IIP-25.BMC.Read-only methods
+ * Returns BTP Address of BMC
+ *
+ * @return String (BTP Address of BMC)
+ */
+ @External(readonly = true)
+ String getBtpAddress();
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCMock.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCMock.java
new file mode 100644
index 00000000..dec4ec00
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCMock.java
@@ -0,0 +1,56 @@
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.btp.lib.BTPAddress;
+import score.Address;
+import score.Context;
+import score.DictDB;
+import score.annotation.External;
+
+import java.math.BigInteger;
+
+public class BMCMock {
+ private final DictDB bshServices = Context.newDictDB("bshService", Address.class);
+ private final BTPAddress btpAddr;
+
+ public BMCMock(String _net) {
+ this.btpAddr = new BTPAddress(BTPAddress.PROTOCOL_BTP, _net, Context.getAddress().toString());
+ }
+
+
+ @External
+ public void addService(String _svc, Address _addr) {
+ bshServices.set(_svc, _addr);
+ }
+
+ @External
+ public void sendMessage(String _to, String _svc, BigInteger _sn, byte[] _msg) {
+ Address addr = bshServices.get(_svc);
+ if (addr == null) {
+ Context.revert("BSH doesnt exist");
+ }
+ if (!Context.getCaller().equals(addr)) {
+ Context.revert("unauthorized");
+ }
+ if (_sn.compareTo(BigInteger.ZERO) < 1) {
+ //Context.revert("invalid sn");
+ }
+ }
+
+ @External
+ public void handleBTPMessage(String from, String svc, BigInteger sn, byte[] msg) {
+ Address _addr = bshServices.get(svc);
+ Context.call(_addr, "handleBTPMessage", from, svc, sn, msg);
+ }
+
+ @External
+ public void handleFeeGathering(String _fa, String _svc) {
+ Address _addr = bshServices.get(_svc);
+ Context.call(_addr, "handleFeeGathering", _fa, _svc);
+ }
+
+
+ @External(readonly = true)
+ public String getBtpAddress() {
+ return btpAddr.toString();
+ }
+}
\ No newline at end of file
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCScoreInterface.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCScoreInterface.java
new file mode 100644
index 00000000..b6709259
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCScoreInterface.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.Address;
+import score.Context;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+public final class BMCScoreInterface implements BMC {
+ protected final Address address;
+
+ protected final BigInteger valueForPayable;
+
+ public BMCScoreInterface(Address address) {
+ this.address = address;
+ this.valueForPayable = null;
+ }
+
+ public BMCScoreInterface(Address address, BigInteger valueForPayable) {
+ this.address = address;
+ this.valueForPayable = valueForPayable;
+ }
+
+ public Address _getAddress() {
+ return this.address;
+ }
+
+ public BMCScoreInterface _payable(BigInteger valueForPayable) {
+ return new BMCScoreInterface(address,valueForPayable);
+ }
+
+ public BMCScoreInterface _payable(long valueForPayable) {
+ return this._payable(BigInteger.valueOf(valueForPayable));
+ }
+
+ public BigInteger _getICX() {
+ return this.valueForPayable;
+ }
+
+ @Override
+ public void addVerifier(String _net, Address _addr) {
+ Context.call(this.address, "addVerifier", _net, _addr);
+ }
+
+ @Override
+ public void removeVerifier(String _net) {
+ Context.call(this.address, "removeVerifier", _net);
+ }
+
+ @Override
+ public Map getVerifiers() {
+ return Context.call(Map.class, this.address, "getVerifiers");
+ }
+
+ @Override
+ public void addService(String _svc, Address _addr) {
+ Context.call(this.address, "addService", _svc, _addr);
+ }
+
+ @Override
+ public void removeService(String _svc) {
+ Context.call(this.address, "removeService", _svc);
+ }
+
+ @Override
+ public Map getServices() {
+ return Context.call(Map.class, this.address, "getServices");
+ }
+
+ @Override
+ public void addLink(String _link) {
+ Context.call(this.address, "addLink", _link);
+ }
+
+ @Override
+ public void removeLink(String _link) {
+ Context.call(this.address, "removeLink", _link);
+ }
+
+ @Override
+ public BMCStatus getStatus(String _link) {
+ return Context.call(BMCStatus.class, this.address, "getStatus", _link);
+ }
+
+ @Override
+ public String[] getLinks() {
+ return Context.call(String[].class, this.address, "getLinks");
+ }
+
+ @Override
+ public void addRoute(String _dst, String _link) {
+ Context.call(this.address, "addRoute", _dst, _link);
+ }
+
+ @Override
+ public void removeRoute(String _dst) {
+ Context.call(this.address, "removeRoute", _dst);
+ }
+
+ @Override
+ public Map getRoutes() {
+ return Context.call(Map.class, this.address, "getRoutes");
+ }
+
+ @Override
+ public void sendMessage(String _to, String _svc, BigInteger _sn, byte[] _msg) {
+ Context.call(this.address, "sendMessage", _to, _svc, _sn, _msg);
+ }
+
+ @Override
+ public void handleRelayMessage(String _prev, String _msg) {
+ Context.call(this.address, "handleRelayMessage", _prev, _msg);
+ }
+
+ @Override
+ public String getBtpAddress() {
+ return Context.call(String.class, this.address, "getBtpAddress");
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCStatus.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCStatus.java
new file mode 100644
index 00000000..a4fb6410
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMCStatus.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.score.util.StringUtil;
+import score.annotation.Keep;
+
+import java.math.BigInteger;
+
+public class BMCStatus {
+ private BigInteger rx_seq;
+ private BigInteger tx_seq;
+ private BMVStatus verifier;
+ private BMRStatus[] relays;
+
+ private int block_interval_src;
+ private int block_interval_dst;
+ private int max_agg;
+ private int delay_limit;
+
+ private int relay_idx;
+ private long rotate_height;
+ private int rotate_term;
+ private long rx_height; //initialize with BMC.block_height
+ private long rx_height_src; //initialize with BMV._offset
+
+ private int sack_term; //0: disable sack
+ private long sack_next;
+ private long sack_height;
+ private BigInteger sack_seq;
+
+ private long cur_height;
+
+ @Keep
+ public BigInteger getRx_seq() {
+ return rx_seq;
+ }
+
+ @Keep
+ public void setRx_seq(BigInteger rx_seq) {
+ this.rx_seq = rx_seq;
+ }
+
+ @Keep
+ public BigInteger getTx_seq() {
+ return tx_seq;
+ }
+
+ @Keep
+ public void setTx_seq(BigInteger tx_seq) {
+ this.tx_seq = tx_seq;
+ }
+
+ @Keep
+ public BMVStatus getVerifier() {
+ return verifier;
+ }
+
+ @Keep
+ public void setVerifier(BMVStatus verifier) {
+ this.verifier = verifier;
+ }
+
+ @Keep
+ public BMRStatus[] getRelays() {
+ return relays;
+ }
+
+ @Keep
+ public void setRelays(BMRStatus[] relays) {
+ this.relays = relays;
+ }
+
+ @Keep
+ public int getBlock_interval_src() {
+ return block_interval_src;
+ }
+
+ @Keep
+ public void setBlock_interval_src(int block_interval_src) {
+ this.block_interval_src = block_interval_src;
+ }
+
+ @Keep
+ public int getBlock_interval_dst() {
+ return block_interval_dst;
+ }
+
+ @Keep
+ public void setBlock_interval_dst(int block_interval_dst) {
+ this.block_interval_dst = block_interval_dst;
+ }
+
+ @Keep
+ public int getMax_agg() {
+ return max_agg;
+ }
+
+ @Keep
+ public void setMax_agg(int max_agg) {
+ this.max_agg = max_agg;
+ }
+
+ @Keep
+ public int getDelay_limit() {
+ return delay_limit;
+ }
+
+ @Keep
+ public void setDelay_limit(int delay_limit) {
+ this.delay_limit = delay_limit;
+ }
+
+ @Keep
+ public int getRelay_idx() {
+ return relay_idx;
+ }
+
+ @Keep
+ public void setRelay_idx(int relay_idx) {
+ this.relay_idx = relay_idx;
+ }
+
+ @Keep
+ public long getRotate_height() {
+ return rotate_height;
+ }
+
+ @Keep
+ public void setRotate_height(long rotate_height) {
+ this.rotate_height = rotate_height;
+ }
+
+ @Keep
+ public int getRotate_term() {
+ return rotate_term;
+ }
+
+ @Keep
+ public void setRotate_term(int rotate_term) {
+ this.rotate_term = rotate_term;
+ }
+
+ @Keep
+ public long getRx_height() {
+ return rx_height;
+ }
+
+ @Keep
+ public void setRx_height(long rx_height) {
+ this.rx_height = rx_height;
+ }
+
+ @Keep
+ public long getRx_height_src() {
+ return rx_height_src;
+ }
+
+ @Keep
+ public void setRx_height_src(long rx_height_src) {
+ this.rx_height_src = rx_height_src;
+ }
+
+ @Keep
+ public int getSack_term() {
+ return sack_term;
+ }
+
+ @Keep
+ public void setSack_term(int sack_term) {
+ this.sack_term = sack_term;
+ }
+
+ @Keep
+ public long getSack_next() {
+ return sack_next;
+ }
+
+ @Keep
+ public void setSack_next(long sack_next) {
+ this.sack_next = sack_next;
+ }
+
+ @Keep
+ public long getSack_height() {
+ return sack_height;
+ }
+
+ @Keep
+ public void setSack_height(long sack_height) {
+ this.sack_height = sack_height;
+ }
+
+ @Keep
+ public BigInteger getSack_seq() {
+ return sack_seq;
+ }
+
+ @Keep
+ public void setSack_seq(BigInteger sack_seq) {
+ this.sack_seq = sack_seq;
+ }
+
+ @Keep
+ public long getCur_height() {
+ return cur_height;
+ }
+
+ @Keep
+ public void setCur_height(long cur_height) {
+ this.cur_height = cur_height;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BMCStatus{");
+ sb.append("rx_seq=").append(rx_seq);
+ sb.append(", tx_seq=").append(tx_seq);
+ sb.append(", verifier=").append(verifier);
+ sb.append(", relays=").append(StringUtil.toString(relays));
+ sb.append(", block_interval_src=").append(block_interval_src);
+ sb.append(", block_interval_dst=").append(block_interval_dst);
+ sb.append(", max_agg=").append(max_agg);
+ sb.append(", delay_limit=").append(delay_limit);
+ sb.append(", relay_idx=").append(relay_idx);
+ sb.append(", rotate_height=").append(rotate_height);
+ sb.append(", rotate_term=").append(rotate_term);
+ sb.append(", rx_height=").append(rx_height);
+ sb.append(", rx_height_src=").append(rx_height_src);
+ sb.append(", sack_term=").append(sack_term);
+ sb.append(", sack_next=").append(sack_next);
+ sb.append(", sack_height=").append(sack_height);
+ sb.append(", sack_seq=").append(sack_seq);
+ sb.append(", cur_height=").append(cur_height);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMRStatus.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMRStatus.java
new file mode 100644
index 00000000..eb02fc66
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMRStatus.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.Address;
+import score.annotation.Keep;
+
+import java.math.BigInteger;
+
+public class BMRStatus {
+ private Address address;
+ private long block_count;
+ private BigInteger msg_count;
+
+ @Keep
+ public Address getAddress() {
+ return address;
+ }
+
+ @Keep
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ @Keep
+ public long getBlock_count() {
+ return block_count;
+ }
+
+ @Keep
+ public void setBlock_count(long block_count) {
+ this.block_count = block_count;
+ }
+
+ @Keep
+ public BigInteger getMsg_count() {
+ return msg_count;
+ }
+
+ @Keep
+ public void setMsg_count(BigInteger msg_count) {
+ this.msg_count = msg_count;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BMRStatus{");
+ sb.append("address=").append(address);
+ sb.append(", block_count=").append(block_count);
+ sb.append(", msg_count=").append(msg_count);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMVStatus.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMVStatus.java
new file mode 100644
index 00000000..2c9a786d
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/BMVStatus.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.annotation.Keep;
+
+public class BMVStatus {
+ private long height;
+ private long offset;
+ private long last_height;
+
+ @Keep
+ public BMVStatus() {
+ }
+
+ @Keep
+ public long getHeight() {
+ return height;
+ }
+
+ @Keep
+ public void setHeight(long height) {
+ this.height = height;
+ }
+
+ @Keep
+ public long getOffset() {
+ return offset;
+ }
+
+ @Keep
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ @Keep
+ public long getLast_height() {
+ return last_height;
+ }
+
+ @Keep
+ public void setLast_height(long last_height) {
+ this.last_height = last_height;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BMVStatus{");
+ sb.append("height=").append(height);
+ sb.append(", offset=").append(offset);
+ sb.append(", last_height=").append(last_height);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Balance.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Balance.java
new file mode 100644
index 00000000..1e57b931
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/Balance.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+
+import java.math.BigInteger;
+
+public class Balance {
+
+ private BigInteger usable;
+ private BigInteger locked;
+ private BigInteger refundable;
+
+
+ public Balance(BigInteger usable, BigInteger locked, BigInteger refundable) {
+ this.usable = usable;
+ this.locked = locked;
+ this.refundable = refundable;
+ }
+
+ public BigInteger getUsable() {
+ return usable;
+ }
+
+ public void setUsable(BigInteger usable) {
+ this.usable = usable;
+ }
+
+ public BigInteger getLocked() {
+ return locked;
+ }
+
+ public void setLocked(BigInteger locked) {
+ this.locked = locked;
+ }
+
+ public BigInteger getRefundable() {
+ return refundable;
+ }
+
+ public void setRefundable(BigInteger refundable) {
+ this.refundable = refundable;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Balance{");
+ sb.append("usable=").append(usable);
+ sb.append(", locked=").append(locked);
+ sb.append(", refundable=").append(refundable);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, Balance obj) {
+ obj.writeObject(writer);
+ }
+
+ public static Balance readObject(ObjectReader reader) {
+ reader.beginList();
+ //obj.setUsable(reader.readNullable(BigInteger.class));
+ //obj.setLocked(reader.readNullable(BigInteger.class));
+ //obj.setRefundable(reader.readNullable(BigInteger.class));
+ Balance obj = new Balance(reader.readNullable(BigInteger.class),reader.readNullable(BigInteger.class),reader.readNullable(BigInteger.class));
+ reader.end();
+ return obj;
+ }
+
+ public void writeObject(ObjectWriter writer) {
+ writer.beginList(3);
+ writer.writeNullable(this.getUsable());
+ writer.writeNullable(this.getLocked());
+ writer.writeNullable(this.getRefundable());
+ writer.end();
+ }
+
+ public static Balance fromBytes(byte[] bytes) {
+ ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes);
+ return Balance.readObject(reader);
+ }
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ Balance.writeObject(writer, this);
+ return writer.toByteArray();
+ }
+
+}
\ No newline at end of file
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCS.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCS.java
new file mode 100644
index 00000000..5a2cff69
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCS.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.Address;
+import score.annotation.External;
+import score.annotation.Payable;
+
+import java.math.BigInteger;
+
+public interface NCS {
+
+ /**
+ * Registers a wrapped token smart contract and id number of a supporting coin.
+ * Caller must be the owner
+ *
+ * {@code _name} It must be listed in the code name registry..
+ * The native coin (ICX) must be registered through the constructor.
+ *
+ * @param _name A coin name.
+ */
+ @External
+ void register(String _name);
+
+ /**
+ * Return all supported coins names in other networks by the BSH contract
+ *
+ * @return An array of strings.
+ */
+ @External(readonly = true)
+ String[] coinNames();
+
+ /**
+ * Return an _id number and a symbol of Coin whose name is the same with given _coinName.
+ *
+ * @implSpec Return nullempty if not found.
+ *
+ * @param _coinName Coin name.
+ * @return id of Native Coin Token.
+ */
+ @External(readonly = true)
+ BigInteger coinId(String _coinName);
+
+ /**
+ * Return a usable/locked balance of an account based on coinName.
+ *
+ * @implSpec Locked Balance means an amount of Coins/Wrapped Coins is currently
+ * at a pending state when a transfer tx is requested from this chain to another
+ * @implSpec Return 0 if not found
+ *
+ * @param _owner
+ * @param _coinName Coin name.
+ * @return Balance
+ * {
+ * "locked" : an amount of locked Coins/WrappedCoins,
+ * "refundable" : an amount of refundable Coins/WrappedCoins
+ * }
+ */
+ @External(readonly = true)
+ Balance balanceOf(Address _owner, String _coinName);
+
+ /**
+ * Return a list locked/usable balance of an account.
+ *
+ * @implSpec The order of request's coinNames must be the same with the order of return balance
+ * @implSpec Return 0 if not found.
+ *
+ * @param _owner
+ * @param _coinNames
+ * @return Balance[]
+ * [
+ * {
+ * "locked" : an amount of locked Coins/WrappedCoins,
+ * "refundable" : an amount of refundable Coins/WrappedCoins
+ * }
+ * ]
+ */
+ @External(readonly = true)
+ Balance[] balanceOfBatch(Address _owner, String[] _coinNames);
+
+ /**
+ * Reclaim the coin's refundable balance by an owner.
+ *
+ * @apiNote Caller must be an owner of coin
+ * @implSpec This function only applies on native coin (not wrapped coins)
+ * The amount to claim must be smaller than refundable balance
+ *
+ * @param _coinName A given name of coin to be re-claim
+ * @param _value An amount of re-claiming
+ */
+ @External
+ void reclaim(String _coinName, BigInteger _value);
+
+ /**
+ * Allow users to deposit `msg.value` native coin into a BSH contract.
+ *
+ * @implSpec MUST specify msg.value
+ *
+ * @param _to An address that a user expects to receive an equivalent amount of tokens.
+ */
+ @Payable
+ @External
+ void transferNativeCoin(String _to);
+
+ /**
+ * Allow users to deposit an amount of wrapped native coin `_coinName` from the `msg.sender` address into the BSH contract.
+ *
+ * @apiNote Caller must set to approve that the wrapped tokens can be transferred out of the `msg.sender` account by the operator.
+ * @implSpec It MUST revert if the balance of the holder for token `_coinName` is lower than the `_value` sent.
+ *
+ * @param _coinName A given name of coin that is equivalent to retrieve a wrapped Token Contract's address, i.e. list["Token A"] = 0x12345678
+ * @param _value Transferring amount.
+ * @param _to Target address.
+ */
+ @External
+ void transfer(String _coinName, BigInteger _value, String _to);
+
+ /**
+ * Allows users to deposit `_values` amounts of coins `_coinName` from the `msg.sender` address into the BSH contract.
+ *
+ * The caller must set to approve that the wrapped tokens can be transferred out of the `msg.sender` account by the contract. It MUST revert if the balance of the holder for token `_coinName` is lower than the `_value` sent.
+ * The order of coinName and value should be matched
+ *
+ * @param _coinNames Given names of each coin that is equivalent to retrieve a wrapped Token Contract's address, i.e. list["Token A"] = 0x12345678
+ * @param _values Transferring amounts per coin
+ * @param _to Target BTP Address.
+ */
+ @Payable
+ @External
+ void transferBatch(String[] _coinNames, BigInteger[] _values, String _to);
+
+ /**
+ * Sets a new transfer fee ratio.
+ *
+ * The caller must be the owner.
+ * The transfer fee is calculated by feeNumerator/FEE_DEMONINATOR. The feeNumetator should be less than FEE_DEMONINATOR and greater than 1
+ * feeNumetator is set to `10` in construction by default, which means the default fee ratio is 0.1%.
+ *
+ * @param _feeNumerator the fee numerator
+ */
+ @External
+ void setFeeRatio(BigInteger _feeNumerator);
+
+ /**
+ * Get transfer fee ratio.
+ *
+ * @return BigInteger the fee ratio
+ *
+ */
+ @External(readonly = true)
+ BigInteger feeRatio();
+
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSEvents.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSEvents.java
new file mode 100644
index 00000000..fc700104
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSEvents.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.Address;
+import score.annotation.EventLog;
+
+import java.math.BigInteger;
+
+public interface NCSEvents {
+
+ /**
+ * (EventLog) Sends a receipt to sender
+ *
+ * @param _from The {@code _from} sender. (Indexed)
+ * @param _to The {@code _to} receiver.
+ * @param _sn The {@code _sn} sequence number of the service message.
+ * @param _assets The {@code _assets} asset details that is the serialized data of AssetTransferDetail
+ */
+ @EventLog(indexed = 1)
+ void TransferStart(Address _from, String _to, BigInteger _sn, byte[] _assets);
+
+ /**
+ * (EventLog) Sends a receipt to sender to notify the transfer's result
+ *
+ * @param _sender The {@code _sender} account sends the service message. (Indexed)
+ * @param _sn The {@code _sn} sequence number of the service message.
+ * @param _code The {@code _code} response code.
+ * @param _msg The {@code _msg} response message.
+ */
+ @EventLog(indexed = 1)
+ void TransferEnd(Address _sender, BigInteger _sn, BigInteger _code, byte[] _msg);
+
+ /**
+ * Notify to the BSH owner that it has received unknown response
+ *
+ * @param _from The {@code _from} Network Address of source network.
+ * @param _sn The {@code _sn} sequence number of the service message.
+ */
+ @EventLog(indexed = 1)
+ void UnknownResponse(String _from, BigInteger _sn);
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSException.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSException.java
new file mode 100644
index 00000000..9a5e8d54
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSException.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.btp.lib.BTPException;
+
+public class NCSException extends BTPException.BSH {
+
+ public NCSException(Code c) {
+ super(c, c.name());
+ }
+
+ public NCSException(Code c, String message) {
+ super(c, message);
+ }
+
+ public static NCSException unknown(String message) {
+ return new NCSException(Code.Unknown, message);
+ }
+
+ public static NCSException unauthorized() {
+ return new NCSException(Code.Unauthorized);
+ }
+ public static NCSException unauthorized(String message) {
+ return new NCSException(Code.Unauthorized, message);
+ }
+
+ public static NCSException irc31Failure() {
+ return new NCSException(Code.IRC31Failure);
+ }
+ public static NCSException irc31Failure(String message) {
+ return new NCSException(Code.IRC31Failure, message);
+ }
+
+ public static NCSException irc31Reverted() {
+ return new NCSException(Code.IRC31Reverted);
+ }
+ public static NCSException irc31Reverted(String message) {
+ return new NCSException(Code.IRC31Reverted, message);
+ }
+
+
+ //BTPException.BSH => 40 ~ 54
+ public enum Code implements BTPException.Coded{
+ Unknown(0),
+ Unauthorized(1),
+ IRC31Failure(2),
+ IRC31Reverted(3);
+
+ final int code;
+ Code(int code){ this.code = code; }
+
+ @Override
+ public int code() { return code; }
+
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSMessage.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSMessage.java
new file mode 100644
index 00000000..8d71a4b8
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSMessage.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.score.util.StringUtil;
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+
+public class NCSMessage {
+ public static int REQUEST_COIN_TRANSFER = 0;
+ public static int REQUEST_COIN_REGISTER = 1;
+ public static int REPONSE_HANDLE_SERVICE = 2;
+ public static int UNKNOWN_TYPE = 3;
+
+ private int serviceType;
+ private byte[] data;
+
+ public int getServiceType() {
+ return serviceType;
+ }
+
+ public void setServiceType(int serviceType) {
+ this.serviceType = serviceType;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public void setData(byte[] data) {
+ this.data = data;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ServiceMessage{");
+ sb.append("serviceType='").append(serviceType).append('\'');
+ sb.append(", data=").append(StringUtil.toString(data));
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, NCSMessage obj) {
+ obj.writeObject(writer);
+ }
+
+ public static NCSMessage readObject(ObjectReader reader) {
+ NCSMessage obj = new NCSMessage();
+ reader.beginList();
+ obj.setServiceType(reader.readInt());
+ obj.setData(reader.readNullable(byte[].class));
+ reader.end();
+ return obj;
+ }
+
+ public void writeObject(ObjectWriter writer) {
+ writer.beginList(2);
+ writer.write(this.getServiceType());
+ writer.writeNullable(this.getData());
+ writer.end();
+ }
+
+ public static NCSMessage fromBytes(byte[] bytes) {
+ ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes);
+ return NCSMessage.readObject(reader);
+ }
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ NCSMessage.writeObject(writer, this);
+ return writer.toByteArray();
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSProperties.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSProperties.java
new file mode 100644
index 00000000..bf5c4646
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NCSProperties.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+
+import java.math.BigInteger;
+
+public class NCSProperties {
+ public static final NCSProperties DEFAULT;
+
+ static {
+ DEFAULT = new NCSProperties();
+ DEFAULT.setSn(BigInteger.ZERO);
+ DEFAULT.setFeeRatio(BigInteger.valueOf(10));
+ }
+
+ private BigInteger sn;
+ private BigInteger feeRatio;
+
+ public BigInteger getSn() {
+ return sn;
+ }
+
+ public void setSn(BigInteger sn) {
+ this.sn = sn;
+ }
+
+ public BigInteger getFeeRatio() {
+ return feeRatio;
+ }
+
+ public void setFeeRatio(BigInteger feeRatio) {
+ this.feeRatio = feeRatio;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("NCSProperties{");
+ sb.append("sn=").append(sn);
+ sb.append(", feeRatio=").append(feeRatio);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, NCSProperties obj) {
+ obj.writeObject(writer);
+ }
+
+ public static NCSProperties readObject(ObjectReader reader) {
+ NCSProperties obj = new NCSProperties();
+ reader.beginList();
+ obj.setSn(reader.readNullable(BigInteger.class));
+ obj.setFeeRatio(reader.readNullable(BigInteger.class));
+ reader.end();
+ return obj;
+ }
+
+ public void writeObject(ObjectWriter writer) {
+ writer.beginList(2);
+ writer.writeNullable(this.getSn());
+ writer.writeNullable(this.getFeeRatio());
+ writer.end();
+ }
+
+ public static NCSProperties fromBytes(byte[] bytes) {
+ ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes);
+ return NCSProperties.readObject(reader);
+ }
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ NCSProperties.writeObject(writer, this);
+ return writer.toByteArray();
+ }
+
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NativeCoinService.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NativeCoinService.java
new file mode 100644
index 00000000..75a6ed98
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/NativeCoinService.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.btp.lib.BSH;
+import foundation.icon.btp.lib.BTPAddress;
+import foundation.icon.btp.lib.OwnerManager;
+import foundation.icon.btp.lib.OwnerManagerImpl;
+import foundation.icon.score.util.ArrayUtil;
+import foundation.icon.score.util.Logger;
+import foundation.icon.score.util.StringUtil;
+import score.*;
+import score.annotation.EventLog;
+import score.annotation.External;
+import score.annotation.Optional;
+import score.annotation.Payable;
+import scorex.util.ArrayList;
+
+import java.math.BigInteger;
+import java.util.List;
+
+public class NativeCoinService implements NCSEvents, BSH, OwnerManager {
+ private static final Logger logger = Logger.getLogger(NativeCoinService.class);
+
+ public static final String SERVICE = "nativecoin";
+ public static final BigInteger NATIVE_COIN_ID = BigInteger.ZERO;
+ public static final BigInteger FEE_DENOMINATOR = BigInteger.valueOf(10000);
+ protected static final Address ZERO_ADDRESS = new Address(new byte[Address.LENGTH]);
+
+ //
+ private final Address bmc;
+ private final String net;
+ private final Address irc2;
+ private final String name;
+ private final String tokenName;
+ private final VarDB properties = Context.newVarDB("properties", NCSProperties.class);
+
+ //
+ private final OwnerManager ownerManager = new OwnerManagerImpl("owners");
+
+ //
+ private final ArrayDB coinNames = Context.newArrayDB("coinNames", String.class);
+ private final BranchDB> balances = Context.newBranchDB("balances", Balance.class);
+ private final DictDB feeBalances = Context.newDictDB("feeBalances", BigInteger.class);
+ private final DictDB transactions = Context.newDictDB("transactions", TransferTransaction.class);
+
+ public NativeCoinService(Address _bmc, Address _irc2, String _name, String _tokenName) {
+ bmc = _bmc;
+ BMCScoreInterface bmcInterface = new BMCScoreInterface(bmc);
+ //BTPAddress btpAddress = BTPAddress.valueOf(bmcInterface.getBtpAddress());
+ String btpAddr = (String) Context.call(bmcInterface._getAddress(), "getBtpAddress");
+ BTPAddress btpAddress = BTPAddress.valueOf(btpAddr);
+ net = btpAddress.net();
+ irc2 = _irc2;
+ name = _name;
+ tokenName = _tokenName;
+ coinNames.add(_tokenName);
+ }
+
+ public NCSProperties getProperties() {
+ return properties.getOrDefault(NCSProperties.DEFAULT);
+ }
+
+ public void setProperties(NCSProperties properties) {
+ this.properties.set(properties);
+ }
+
+ private boolean isRegistered(String name) {
+ if (tokenName.equals(name)) {
+ return true;
+ }
+ return false;
+ }
+
+ static void require(boolean condition, String message) {
+ if (!condition) {
+ throw NCSException.unknown(message);
+ }
+ }
+
+ @External(readonly = true)
+ public String[] coinNames() {
+ String[] names = new String[2];
+ names[0] = name;
+ names[1] = tokenName;
+ return names;
+ }
+
+ @External(readonly = true)
+ public Balance balanceOf(Address _owner, String _coinName) {
+ if (_owner.equals(Context.getAddress())) {
+ Balance balance = new Balance(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO);
+ balance.setRefundable(feeBalances.getOrDefault(_coinName, BigInteger.ZERO));
+ return balance;
+ } else {
+ return getBalance(_coinName, _owner);
+ }
+ }
+
+ @External
+ public void reclaim(String _coinName, BigInteger _value) {
+ require(_value.compareTo(BigInteger.ZERO) > 0, "_value must be positive");
+
+ Address owner = Context.getCaller();
+ Balance balance = getBalance(_coinName, owner);
+ require(balance.getRefundable().compareTo(_value) > -1, "invalid value");
+ balance.setRefundable(balance.getRefundable().subtract(_value));
+ setBalance(_coinName, owner, balance);
+
+ if (name.equals(_coinName)) {
+ Context.transfer(owner, _value);
+ } else {
+ transfer(irc2, owner, _value, "Reclaim: transferring to Caller".getBytes());
+ }
+ }
+
+ @External
+ public void tokenFallback(Address from, BigInteger value, @Optional byte[] data) {
+ require(value.compareTo(BigInteger.ZERO) > 0, "Invalid Amount");
+ require(Context.getCaller() == irc2, "Invalid Token Address");
+ require(tokenName != null && !tokenName.equals(""), "Token not registered");
+ deposit(tokenName, from, value);
+ }
+
+ @Payable
+ @External
+ public void transferNativeCoin(String _to) {
+ BigInteger value = Context.getValue();
+ require(value != null && value.compareTo(BigInteger.ZERO) > 0, "Invalid amount");
+ sendRequest(Context.getCaller(), BTPAddress.valueOf(_to), List.of(name), List.of(value));
+ }
+
+ @External
+ public void transfer(String _coinName, BigInteger _value, String _to) {
+ require(_value != null && _value.compareTo(BigInteger.ZERO) > 0, "Invalid amount");
+ require(!name.equals(_coinName) && isRegistered(_coinName), "Not supported Token");
+
+ Address owner = Context.getCaller();
+ Balance balance = getBalance(_coinName, owner);
+ require(balance.getUsable().compareTo(_value) >= 0, "Overdrawn: Insufficient Balance");
+ sendRequest(owner, BTPAddress.valueOf(_to), List.of(_coinName), List.of(_value));
+ }
+
+ @EventLog(indexed = 1)
+ public void TransferStart(Address _from, String _to, BigInteger _sn, byte[] _assetDetails) {
+ }
+
+ @EventLog(indexed = 1)
+ public void TransferEnd(Address _from, BigInteger _sn, BigInteger _code, byte[] _msg) {
+ }
+
+ @EventLog(indexed = 1)
+ public void UnknownResponse(String _from, BigInteger _sn) {
+ }
+
+ @External(readonly = true)
+ public TransferTransaction getTransaction(BigInteger _sn) {
+ return transactions.get(_sn);
+ }
+
+ private void sendRequest(Address owner, BTPAddress to, List coinNames, List amounts) {
+ logger.println("sendRequest", "begin");
+ NCSProperties properties = getProperties();
+
+ BigInteger feeRatio = properties.getFeeRatio();
+ if (owner.equals(Context.getAddress())) {
+ feeRatio = BigInteger.ZERO;
+ }
+ int len = coinNames.size();
+ AssetTransferDetail[] assetTransferDetails = new AssetTransferDetail[len];
+ Asset[] assets = new Asset[len];
+ for (int i = 0; i < len; i++) {
+ String coinName = coinNames.get(i);
+ BigInteger amount = amounts.get(i);
+ AssetTransferDetail assetTransferDetail = newAssetTransferDetail(coinName, amount, feeRatio);
+ lock(coinName, owner, amount);
+ assetTransferDetails[i] = assetTransferDetail;
+ assets[i] = new Asset(assetTransferDetail);
+ }
+
+ TransferRequest request = new TransferRequest();
+ request.setFrom(owner.toString());
+ request.setTo(to.account());
+ request.setAssets(assets);
+
+ TransferTransaction transaction = new TransferTransaction();
+ transaction.setFrom(owner.toString());
+ transaction.setTo(to.toString());
+ transaction.setAssets(assetTransferDetails);
+
+ BigInteger sn = properties.getSn().add(BigInteger.ONE);
+ properties.setSn(sn);
+ setProperties(properties);
+ transactions.set(sn, transaction);
+
+ sendMessage(to.net(), NCSMessage.REQUEST_COIN_TRANSFER, sn, request.toBytes());
+ TransferStart(owner, to.toString(), sn, encode(assetTransferDetails));
+ logger.println("sendRequest", "end");
+ }
+
+ static byte[] encode(AssetTransferDetail[] assetTransferDetails) {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ writer.beginList(assetTransferDetails.length);
+ for (AssetTransferDetail v : assetTransferDetails) {
+ //writer.write(v);
+ AssetTransferDetail.writeObject(writer, v);
+ }
+ writer.end();
+ return writer.toByteArray();
+ }
+
+ private void sendMessage(String to, int serviceType, BigInteger sn, byte[] data) {
+ logger.println("sendMessage", "begin");
+ NCSMessage message = new NCSMessage();
+ message.setServiceType(serviceType);
+ message.setData(data);
+ Context.call(bmc, "sendMessage", to, SERVICE, sn, message.toBytes());
+ logger.println("sendMessage", "end");
+ }
+
+ private void responseSuccess(String to, BigInteger sn) {
+ TransferResponse response = new TransferResponse();
+ response.setCode(TransferResponse.RC_OK);
+ response.setMessage(TransferResponse.OK_MSG);
+ sendMessage(to, NCSMessage.REPONSE_HANDLE_SERVICE, sn, response.toBytes());
+ }
+
+ private void responseError(String to, BigInteger sn, String message) {
+ TransferResponse response = new TransferResponse();
+ response.setCode(TransferResponse.RC_ERR);
+ response.setMessage(message);
+ sendMessage(to, NCSMessage.REPONSE_HANDLE_SERVICE, sn, response.toBytes());
+ }
+
+ @External
+ public void handleBTPMessage(String _from, String _svc, BigInteger _sn, byte[] _msg) {
+ require(Context.getCaller().equals(bmc), "Only BMC");
+
+ NCSMessage message = NCSMessage.fromBytes(_msg);
+ int serviceType = message.getServiceType();
+ if (serviceType == NCSMessage.REQUEST_COIN_TRANSFER) {
+ TransferRequest request = TransferRequest.fromBytes(message.getData());
+ handleRequest(request, _from, _sn);
+ } else if (serviceType == NCSMessage.REPONSE_HANDLE_SERVICE) {
+ TransferResponse response = TransferResponse.fromBytes(message.getData());
+ handleResponse(_sn, response);
+ } else if (serviceType == NCSMessage.UNKNOWN_TYPE) {
+ // If receiving a RES_UNKNOWN_TYPE, ignore this message
+ // or re-send another correct message
+ UnknownResponse(_from, _sn);
+ } else {
+ // If none of those types above, BSH responds a message of RES_UNKNOWN_TYPE
+ TransferResponse response = new TransferResponse();
+ response.setCode(TransferResponse.RC_ERR);
+ response.setMessage(TransferResponse.ERR_MSG_UNKNOWN_TYPE);
+ sendMessage(_from, NCSMessage.UNKNOWN_TYPE, _sn, response.toBytes());
+ }
+ }
+
+ @External
+ public void handleBTPError(String _src, String _svc, BigInteger _sn, long _code, String _msg) {
+ require(Context.getCaller().equals(bmc), "Only BMC");
+ TransferResponse response = new TransferResponse();
+ response.setCode(TransferResponse.RC_ERR);
+ response.setMessage("BTPError [code:" + _code + ",msg:" + _msg);
+ handleResponse(_sn, response);
+ }
+
+ @External
+ public void handleFeeGathering(String _fa, String _svc) {
+ require(Context.getCaller().equals(bmc), "Only BMC");
+ BTPAddress from = BTPAddress.valueOf(_fa);
+ Address owner = Context.getAddress();
+
+ List coinNames = new ArrayList<>();
+ List feeAmounts = new ArrayList<>();
+ for (String coinName : coinNames()) {
+ BigInteger feeAmount = clearFee(coinName);
+ if (feeAmount.compareTo(BigInteger.ZERO) > 0) {
+ coinNames.add(coinName);
+ feeAmounts.add(feeAmount);
+ }
+ }
+
+ if (coinNames.size() > 0) {
+ if (from.net().equals(net)) {
+ Address fa = Address.fromString(from.account());
+ int idx = coinNames.indexOf(name);
+ if (idx >= 0) {
+ coinNames.remove(idx);
+ BigInteger feeAmount = feeAmounts.remove(idx);
+ Context.transfer(fa, feeAmount);
+ }
+ _transferBatch(fa, ArrayUtil.toStringArray(coinNames), ArrayUtil.toBigIntegerArray(feeAmounts));
+ } else {
+ sendRequest(owner, from, coinNames, feeAmounts);
+ }
+ }
+ }
+
+ private void _transferBatch(Address to, String[] coinNames, BigInteger[] amounts) {
+ logger.println("transferBatch", to, StringUtil.toString(coinNames), StringUtil.toString(amounts));
+ for (int i = 0; i < coinNames.length; i++) {
+ _transfer(irc2, to, amounts[i]);
+ }
+ }
+
+ private Balance getBalance(String coinName, Address owner) {
+ Balance balance = balances.at(coinName).get(owner);
+ if (balance == null) {
+ balance = new Balance(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO);
+ return balance;
+ }
+ Balance newBal = new Balance(balance.getUsable(), balance.getLocked(), balance.getRefundable());
+ return newBal;
+ }
+
+ private void setBalance(String coinName, Address owner, Balance balance) {
+ Balance newBalance = new Balance(balance.getUsable(), balance.getLocked(), balance.getRefundable());
+ balances.at(coinName).set(owner, newBalance);
+ }
+
+ private void lock(String coinName, Address owner, BigInteger value) {
+ logger.println("lock", "coinName:", coinName, "owner:", owner, "value:", value);
+ Balance balance = getBalance(coinName, owner);
+ balance.setUsable(balance.getUsable().subtract(value));
+ balance.setLocked(balance.getLocked().add(value));
+ setBalance(coinName, owner, balance);
+ }
+
+ private void unlock(String coinName, Address owner, BigInteger value) {
+ logger.println("unlock", "coinName:", coinName, "owner:", owner, "value:", value);
+ Balance balance = getBalance(coinName, owner);
+ balance.setLocked(balance.getLocked().subtract(value));
+ setBalance(coinName, owner, balance);
+ }
+
+ private void refund(String coinName, Address owner, BigInteger value) {
+ logger.println("refund", "coinName:", coinName, "owner:", owner, "value:", value);
+ //unlock and add refundable
+ Balance balance = getBalance(coinName, owner);
+ balance.setLocked(balance.getLocked().subtract(value));
+ if (!owner.equals(Context.getAddress())) {
+ balance.setRefundable(balance.getRefundable().add(value));
+ }
+ setBalance(coinName, owner, balance);
+ }
+
+ private void deposit(String coinName, Address owner, BigInteger value) {
+ logger.println("deposit", "coinName:", coinName, "owner:", owner, "value:", value);
+ Balance balance = getBalance(coinName, owner);
+ balance.setUsable(balance.getUsable().add(value));
+ setBalance(coinName, owner, balance);
+ }
+
+ private void addFee(String coinName, BigInteger amount) {
+ BigInteger fee = feeBalances.getOrDefault(coinName, BigInteger.ZERO);
+ feeBalances.set(coinName, fee.add(amount));
+ }
+
+ private BigInteger clearFee(String coinName) {
+ BigInteger fee = feeBalances.getOrDefault(coinName, BigInteger.ZERO);
+ if (fee.compareTo(BigInteger.ZERO) > 0) {
+ feeBalances.set(coinName, BigInteger.ZERO);
+ }
+ return fee;
+ }
+
+ private void handleRequest(TransferRequest request, String from, BigInteger sn) {
+ logger.println("handleRequest", "begin", "sn:", sn);
+ Address to;
+ try {
+ to = Address.fromString(request.getTo());
+ } catch (IllegalArgumentException | NullPointerException e) {
+ throw NCSException.unknown(e.getMessage());
+ }
+
+ BigInteger nativeCoinTransferAmount = null;
+ Asset[] assets = request.getAssets();
+ for (Asset asset : assets) {
+ String coinName = asset.getCoinName();
+ BigInteger amount = asset.getAmount();
+ if (amount == null || amount.compareTo(BigInteger.ZERO) < 1) {
+ throw NCSException.unknown("Amount must be positive value");
+ }
+
+ if (tokenName.equals(coinName)) {
+ _transfer(irc2, to, amount);
+ } else if (name.equals(coinName)) {
+ nativeCoinTransferAmount = amount;
+ } else {
+ throw NCSException.unknown("Invalid Token");
+ }
+ }
+
+ if (nativeCoinTransferAmount != null) {
+ try {
+ Context.transfer(to, nativeCoinTransferAmount);
+ } catch (Exception e) {
+ throw NCSException.unknown("fail to transfer err:" + e.getMessage());
+ }
+ }
+
+ logger.println("handleRequest", "responseSuccess");
+ responseSuccess(from, sn);
+ logger.println("handleRequest", "end");
+ }
+
+ private void handleResponse(BigInteger sn, TransferResponse response) {
+ logger.println("handleResponse", "begin", "sn:", sn);
+ TransferTransaction transaction = transactions.get(sn);
+ // ignore when not exists pending request
+ if (transaction != null) {
+ BigInteger code = response.getCode();
+ Address owner = Address.fromString(transaction.getFrom());
+ AssetTransferDetail[] assets = transaction.getAssets();
+
+ logger.println("handleResponse", "code:", code);
+ if (TransferResponse.RC_OK.equals(code)) {
+ for (AssetTransferDetail asset : assets) {
+ String coinName = asset.getCoinName();
+ BigInteger amount = asset.getAmount();
+ BigInteger fee = asset.getFee();
+ BigInteger locked = amount.add(fee);
+ boolean isNativeCoin = name.equals(coinName);
+ if (isNativeCoin || tokenName.equals(coinName)) {
+ unlock(coinName, owner, locked);
+ addFee(coinName, fee);
+ if (!isNativeCoin) {
+ _burn(irc2, amount);
+ }
+ } else {
+ //This should not happen
+ throw NCSException.unknown("invalid transaction, invalid coinName");
+ }
+ }
+ } else {
+ for (AssetTransferDetail asset : assets) {
+ String coinName = asset.getCoinName();
+ BigInteger amount = asset.getAmount();
+ BigInteger fee = asset.getFee();
+ BigInteger locked = amount.add(fee);
+ boolean isNativeCoin = name.equals(coinName);
+ if (isNativeCoin || tokenName.equals(coinName)) {
+ refund(coinName, owner, locked);
+ } else {
+ //This should not happen
+ throw NCSException.unknown("invalid transaction, invalid coinName");
+ }
+ }
+ }
+
+ transactions.set(sn, null);
+ TransferEnd(owner, sn, code, response.getMessage() != null ? response.getMessage().getBytes() : null);
+ }
+ logger.println("handleResponse", "end");
+ }
+
+ @External
+ public void setFeeRatio(BigInteger _feeNumerator) {
+ requireOwnerAccess();
+ require(_feeNumerator.compareTo(BigInteger.ONE) >= 0 &&
+ _feeNumerator.compareTo(FEE_DENOMINATOR) < 0,
+ "The feeNumetator should be less than FEE_DEMONINATOR and greater than 1");
+ NCSProperties properties = getProperties();
+ properties.setFeeRatio(_feeNumerator);
+ setProperties(properties);
+ }
+
+ @External(readonly = true)
+ public BigInteger feeRatio() {
+ NCSProperties properties = getProperties();
+ return properties.getFeeRatio();
+ }
+
+ private AssetTransferDetail newAssetTransferDetail(String coinName, BigInteger amount, BigInteger feeRatio) {
+ logger.println("newAssetTransferDetail", "begin");
+ BigInteger fee = amount.multiply(feeRatio).divide(FEE_DENOMINATOR);
+ if (feeRatio.compareTo(BigInteger.ZERO) > 0 && fee.compareTo(BigInteger.ZERO) == 0) {
+ fee = BigInteger.ONE;
+ }
+ BigInteger transferAmount = amount.subtract(fee);
+ logger.println("newAssetTransferDetail", "amount:", amount, "fee:", fee);
+ if (transferAmount.compareTo(BigInteger.ZERO) < 1) {
+ throw NCSException.unknown("not enough value");
+ }
+ AssetTransferDetail asset = new AssetTransferDetail();
+ asset.setCoinName(coinName);
+ asset.setAmount(transferAmount);
+ asset.setFee(fee);
+ logger.println("newAssetTransferDetail", "end");
+ return asset;
+ }
+
+ private void _transfer(Address token, Address to, BigInteger amount) {
+ logger.println("transfer", to, amount);
+ try {
+ transfer(token, to, amount, "transfer to Receiver".getBytes());
+ } catch (UserRevertedException e) {
+ logger.println("transfer", "code:", e.getCode(), "msg:", e.getMessage());
+ throw NCSException.irc31Reverted("code:" + e.getCode() + "msg:" + e.getMessage());
+ } catch (IllegalArgumentException | RevertedException e) {
+ logger.println("transfer", "Exception:", e.toString());
+ throw NCSException.irc31Failure("Exception:" + e);
+ }
+ }
+
+ private void _burn(Address token, BigInteger amount) {
+ logger.println("burn", ZERO_ADDRESS, amount);
+ try {
+ transfer(token, ZERO_ADDRESS, amount, "Burn Transfer to Zero Address".getBytes());
+ } catch (UserRevertedException e) {
+ logger.println("burn", "code:", e.getCode(), "msg:", e.getMessage());
+ throw NCSException.irc31Reverted("code:" + e.getCode() + "msg:" + e.getMessage());
+ } catch (IllegalArgumentException | RevertedException e) {
+ logger.println("burn", "Exception:", e.toString());
+ throw NCSException.irc31Failure("Exception:" + e);
+ }
+ }
+
+
+ /* Delegate OwnerManager */
+ private void requireOwnerAccess() {
+ if (!ownerManager.isOwner(Context.getCaller())) {
+ throw NCSException.unauthorized("require owner access");
+ }
+ }
+
+ @External
+ public void addOwner(Address _addr) {
+ try {
+ ownerManager.addOwner(_addr);
+ } catch (IllegalStateException e) {
+ throw NCSException.unauthorized(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw NCSException.unknown(e.getMessage());
+ }
+ }
+
+ @External
+ public void removeOwner(Address _addr) {
+ try {
+ ownerManager.removeOwner(_addr);
+ } catch (IllegalStateException e) {
+ throw NCSException.unauthorized(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw NCSException.unknown(e.getMessage());
+ }
+ }
+
+ @External(readonly = true)
+ public Address[] getOwners() {
+ return ownerManager.getOwners();
+ }
+
+ @External(readonly = true)
+ public boolean isOwner(Address _addr) {
+ return ownerManager.isOwner(_addr);
+ }
+
+ public void transfer(Address tokenAddr, Address _to, BigInteger _value, byte[] _data) {
+ Context.call(tokenAddr, "transfer", _to, _value, _data);
+ }
+
+}
\ No newline at end of file
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferAsset.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferAsset.java
new file mode 100644
index 00000000..73c9da60
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferAsset.java
@@ -0,0 +1,74 @@
+package foundation.icon.btp.nativecoinIRC2;
+
+import score.ObjectReader;
+import score.ObjectWriter;
+import scorex.util.ArrayList;
+
+import java.util.List;
+
+public class TransferAsset {
+ private String from;
+ private String to;
+ private List assets;
+ private List test;
+
+ public TransferAsset(String from, String to, List assets) {
+ this.from = from;
+ this.to = to;
+ this.assets = assets;
+ }
+
+
+ public static void writeObject(ObjectWriter w, TransferAsset v) {
+ w.beginList(3);
+ w.write(v.getFrom());
+ w.write(v.getTo());
+ w.beginList(v.getAssets().size());
+ for (int i = 0; i < v.getAssets().size(); i++) {
+ Asset.writeObject(w, v.getAssets().get(i));
+ //w.write( v.getAsset());
+ }
+ w.end();
+ w.end();
+ }
+
+ public static TransferAsset readObject(ObjectReader r) {
+ r.beginList();
+ String _from = r.readString();
+ String _to = r.readString();
+ List assets = new ArrayList<>();
+ r.beginList();
+ while (r.hasNext()) {
+ Asset _asset = Asset.readObject(r);
+ assets.add(_asset);
+ }
+ r.end();
+ r.end();
+ TransferAsset result = new TransferAsset(_from, _to, assets);
+ return result;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ public List getAssets() {
+ return assets;
+ }
+
+ public void setAssets(List assets) {
+ this.assets = assets;
+ }
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferRequest.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferRequest.java
new file mode 100644
index 00000000..7e9564e4
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/TransferRequest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2021 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foundation.icon.btp.nativecoinIRC2;
+
+import foundation.icon.score.util.StringUtil;
+import score.ByteArrayObjectWriter;
+import score.Context;
+import score.ObjectReader;
+import score.ObjectWriter;
+import scorex.util.ArrayList;
+
+import java.util.List;
+
+public class TransferRequest {
+ private String from;
+ private String to;
+ private Asset[] assets;
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public Asset[] getAssets() {
+ return assets;
+ }
+
+ public void setAssets(Asset[] assets) {
+ this.assets = assets;
+ }
+
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TransferRequest{");
+ sb.append("from='").append(from).append('\'');
+ sb.append(", to='").append(to).append('\'');
+ sb.append(", assets=").append(StringUtil.toString(assets));
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static void writeObject(ObjectWriter writer, TransferRequest obj) {
+ obj.writeObject(writer);
+ }
+
+ public static TransferRequest readObject(ObjectReader reader) {
+ TransferRequest obj = new TransferRequest();
+ reader.beginList();
+ obj.setFrom(reader.readNullable(String.class));
+ obj.setTo(reader.readNullable(String.class));
+ if (reader.beginNullableList()) {
+ Asset[] assets = null;
+ List assetsList = new ArrayList<>();
+ while(reader.hasNext()) {
+// assetsList.add(reader.readNullable(Asset.class));
+ assetsList.add(Asset.readObject(reader));
+ }
+ assets = new Asset[assetsList.size()];
+ for(int i=0; i balances = Context.newDictDB("balances", BigInteger.class);
+
+ public IRC2Basic(String _name, String _symbol, int _decimals, BigInteger _initialSupply) {
+ this.name = _name;
+ this.symbol = _symbol;
+ this.decimals = _decimals;
+
+ // decimals must be larger than 0 and less than 21
+ Context.require(this.decimals >= 0);
+ Context.require(this.decimals <= 21);
+ Context.require(_initialSupply.compareTo(BigInteger.ZERO) >= 0);
+ _mint(Context.getCaller(), _initialSupply.multiply(pow10(_decimals)));
+ }
+
+ private static BigInteger pow10(int exponent) {
+ BigInteger result = BigInteger.ONE;
+ for (int i = 0; i < exponent; i++) {
+ result = result.multiply(BigInteger.TEN);
+ }
+ return result;
+ }
+
+ @External(readonly=true)
+ public String name() {
+ return name;
+ }
+
+ @External(readonly=true)
+ public String symbol() {
+ return symbol;
+ }
+
+ @External(readonly=true)
+ public int decimals() {
+ return decimals;
+ }
+
+ @External(readonly=true)
+ public BigInteger totalSupply() {
+ return totalSupply;
+ }
+
+ @External(readonly=true)
+ public BigInteger balanceOf(Address _owner) {
+ return safeGetBalance(_owner);
+ }
+
+ @External
+ public void transfer(Address _to, BigInteger _value, @Optional byte[] _data) {
+ Address _from = Context.getCaller();
+
+ // check some basic requirements
+ Context.require(_value.compareTo(BigInteger.ZERO) >= 0);
+ Context.require(safeGetBalance(_from).compareTo(_value) >= 0);
+
+ // adjust the balances
+ safeSetBalance(_from, safeGetBalance(_from).subtract(_value));
+ safeSetBalance(_to, safeGetBalance(_to).add(_value));
+
+ // if the recipient is SCORE, call 'tokenFallback' to handle further operation
+ byte[] dataBytes = (_data == null) ? new byte[0] : _data;
+ if (_to.isContract()) {
+ Context.call(_to, "tokenFallback", _from, _value, dataBytes);
+ }
+
+ // emit Transfer event
+ Transfer(_from, _to, _value, dataBytes);
+ }
+
+ /**
+ * Creates `amount` tokens and assigns them to `owner`, increasing the total supply.
+ */
+ protected void _mint(Address owner, BigInteger amount) {
+ Context.require(!ZERO_ADDRESS.equals(owner));
+ Context.require(amount.compareTo(BigInteger.ZERO) >= 0);
+
+ this.totalSupply = this.totalSupply.add(amount);
+ safeSetBalance(owner, safeGetBalance(owner).add(amount));
+ Transfer(ZERO_ADDRESS, owner, amount, "mint".getBytes());
+ }
+
+ /**
+ * Destroys `amount` tokens from `owner`, reducing the total supply.
+ */
+ protected void _burn(Address owner, BigInteger amount) {
+ Context.require(!ZERO_ADDRESS.equals(owner));
+ Context.require(amount.compareTo(BigInteger.ZERO) >= 0);
+ Context.require(safeGetBalance(owner).compareTo(amount) >= 0);
+
+ safeSetBalance(owner, safeGetBalance(owner).subtract(amount));
+ this.totalSupply = this.totalSupply.subtract(amount);
+ Transfer(owner, ZERO_ADDRESS, amount, "burn".getBytes());
+ }
+
+ private BigInteger safeGetBalance(Address owner) {
+ return balances.getOrDefault(owner, BigInteger.ZERO);
+ }
+
+ private void safeSetBalance(Address owner, BigInteger amount) {
+ balances.set(owner, amount);
+ }
+
+ @EventLog(indexed=3)
+ public void Transfer(Address _from, Address _to, BigInteger _value, byte[] _data) {}
+}
diff --git a/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/irc2/IRC2ScoreInterface.java b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/irc2/IRC2ScoreInterface.java
new file mode 100644
index 00000000..0531089d
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/main/java/foundation/icon/btp/nativecoinIRC2/irc2/IRC2ScoreInterface.java
@@ -0,0 +1,52 @@
+package foundation.icon.btp.nativecoinIRC2.irc2;
+
+import score.Address;
+import score.Context;
+
+import java.math.BigInteger;
+
+public class IRC2ScoreInterface {
+ protected final Address address;
+
+ public IRC2ScoreInterface(Address address) {
+ this.address = address;
+ }
+
+ public Address _address() {
+ return this.address;
+ }
+
+ public Address _check() {
+ return this.address;
+ }
+
+ public BigInteger _balanceOf(Address _owner) {
+ return (BigInteger)Context.call( this.address, "balanceOf", _owner);
+ //return BigInteger.valueOf(1);
+ }
+
+ public void transfer(Address _to, BigInteger _value, byte[] _data) {
+ Context.call(this.address, "transfer", _to, _value, _data);
+ }
+
+ public void _Transfer(Address _from, Address _to, BigInteger _value, byte[] _data) {
+ throw new RuntimeException("not supported EventLog method");
+ }
+
+ public String _name() {
+ return (String)Context.call(this.address, "name");
+ }
+
+ public String _symbol() {
+ return (String)Context.call(this.address, "symbol");
+ }
+
+ public int _decimals() {
+ return (Integer)Context.call(this.address, "decimals");
+ }
+
+ public BigInteger _totalSupply() {
+ return Context.call(BigInteger.class, this.address, "totalSupply");
+ }
+
+}
diff --git a/javascore/nativecoinIRC2/src/test/java/foundation/icon/btp/nativecoinIRC2/IRC2NativeServiceHandler.java b/javascore/nativecoinIRC2/src/test/java/foundation/icon/btp/nativecoinIRC2/IRC2NativeServiceHandler.java
new file mode 100644
index 00000000..41707e1c
--- /dev/null
+++ b/javascore/nativecoinIRC2/src/test/java/foundation/icon/btp/nativecoinIRC2/IRC2NativeServiceHandler.java
@@ -0,0 +1,117 @@
+package foundation.icon.btp.nativecoinIRC2;
+
+
+import com.iconloop.testsvc.Account;
+import com.iconloop.testsvc.Score;
+import com.iconloop.testsvc.ServiceManager;
+import com.iconloop.testsvc.TestBase;
+import foundation.icon.btp.nativecoinIRC2.irc2.IRC2Basic;
+import org.junit.jupiter.api.*;
+import score.ByteArrayObjectWriter;
+import score.Context;
+import scorex.util.ArrayList;
+
+import java.math.BigInteger;
+import java.util.List;
+
+import static java.math.BigInteger.TEN;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class IRC2NativeServiceHandler extends TestBase {
+ final static String RLPn = "RLPn";
+ final static String _svc = "nativecoin";
+ final static String _net = "icon";
+ final static String tokenName = "MOVR";
+ final static String symbol = "MOVR";
+ final static int decimals = 18;
+ final static BigInteger transferAmount = new BigInteger("10000000000000000000");
+ private static final BigInteger initialSupply = BigInteger.valueOf(2000);
+ private static final BigInteger totalSupply = initialSupply.multiply(TEN.pow(decimals));
+ private static final int REQUEST_TOKEN_TRANSFER = 0;
+ private static final int RESPONSE_HANDLE_SERVICE = 2;
+ private static final ServiceManager sm = getServiceManager();
+ private static Account[] owners;
+ private static Score bsh;
+ private static Score token;
+ private static Score bmc;
+
+ @BeforeAll
+ public static void setup() throws Exception {
+ // setup accounts and deploy
+ owners = new Account[5];
+ for (int i = 0; i < owners.length; i++) {
+ owners[i] = sm.createAccount(100);
+ }
+
+ bmc = sm.deploy(owners[0], BMCMock.class, "0x03.icon");
+ token = sm.deploy(owners[0], IRC2Basic.class, tokenName, symbol, decimals, initialSupply);
+ bsh = sm.deploy(owners[0], NativeCoinService.class, bmc.getAddress(), token.getAddress(), "ICX", "MOVR");
+
+ BigInteger balance = (BigInteger) token.call("balanceOf", owners[0].getAddress());
+ assertEquals(totalSupply, balance);
+ }
+
+
+ /**
+ * Secnario#: Transfer IRC2 tokens from Token contract to BSH via fallback - success
+ */
+ @Order(1)
+ @Test
+ public void scenario1() {
+ String _to, _from;
+ _from = _to = "btp://0x97.bsc/0xa36a32c114ee13090e35cb086459a690f5c1f8e8";
+
+ bmc.invoke(owners[0], "addService", _svc, bsh.getAddress());
+ //Add some enough token balance for BSH
+ Balance balanceBefore = ((Balance) bsh.call("balanceOf", owners[0].getAddress(), tokenName));
+ BigInteger bshTokenBalanceBefore = (BigInteger) token.call("balanceOf", bsh.getAddress());
+ token.invoke(owners[0], "transfer", bsh.getAddress(), transferAmount.multiply(BigInteger.valueOf(10)), new byte[0]);
+ BigInteger bshTokenBalanceAfter = (BigInteger) token.call("balanceOf", bsh.getAddress());
+
+ //Initiate a transfer and check for locked balance
+ bsh.invoke(owners[0], "transfer", tokenName, transferAmount, _to);
+ Balance balanceAfterTransfer = ((Balance) bsh.call("balanceOf", owners[0].getAddress(), tokenName));
+ assertEquals(balanceBefore.getLocked().add(transferAmount), balanceAfterTransfer.getLocked());
+
+ //BSH receives HandleBTPMessgae for token transfer request
+ BigInteger userBalanceBefore = (BigInteger) token.call("balanceOf", owners[1].getAddress());
+ bmc.invoke(owners[0], "handleBTPMessage", _from, _svc, BigInteger.ZERO, handleBTPRequestBtpMsg(_from, owners[1].getAddress().toString()));
+ BigInteger userBalanceAfter = (BigInteger) token.call("balanceOf", owners[1].getAddress());
+ assertEquals(userBalanceBefore.add(transferAmount), userBalanceAfter);
+
+ //Send Success response after successfull transfer
+ Balance balanceBeforeSuccess = (Balance) bsh.call("balanceOf", owners[0].getAddress(), tokenName);
+ bmc.invoke(owners[0], "handleBTPMessage", _from, _svc, BigInteger.ONE, handleBTPResponseBtpMsg(0, "Transfer Success"));
+ Balance balanceAfterSuccess = (Balance) bsh.call("balanceOf", owners[0].getAddress(), tokenName);
+ assertEquals(balanceAfterSuccess.getLocked().add(transferAmount), balanceBeforeSuccess.getLocked());
+ }
+
+ public byte[] handleBTPRequestBtpMsg(String from, String to) {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter(RLPn);
+ writer.beginList(2);
+ writer.write(REQUEST_TOKEN_TRANSFER);//ActionType
+ List assets = new ArrayList();
+ assets.add(new Asset(tokenName, transferAmount));
+ ByteArrayObjectWriter writerTa = Context.newByteArrayObjectWriter(RLPn);
+ TransferAsset _ta = new TransferAsset(from, to, assets);
+ TransferAsset.writeObject(writerTa, _ta);
+ writer.write(writerTa.toByteArray());
+ writer.end();
+ return writer.toByteArray();
+ }
+
+ public byte[] handleBTPResponseBtpMsg(int code, String msg) {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter(RLPn);
+ writer.beginList(2);
+ writer.write(RESPONSE_HANDLE_SERVICE);//ActionType
+ ByteArrayObjectWriter writerRespMsg = Context.newByteArrayObjectWriter(RLPn);
+ writerRespMsg.beginList(2);
+ writerRespMsg.write(code);//Code
+ writerRespMsg.write(msg);//Msg
+ writerRespMsg.end();
+ writer.write(writerRespMsg.toByteArray());
+ writer.end();
+ return writer.toByteArray();
+ }
+}
diff --git a/javascore/settings.gradle b/javascore/settings.gradle
index 2887d619..c89a2fd1 100644
--- a/javascore/settings.gradle
+++ b/javascore/settings.gradle
@@ -6,4 +6,5 @@ include (
'bmc',
'bmv:icon',
'nativecoin',
+ 'nativecoinIRC2',
)
diff --git a/solidity/nativecoinERC20/README.md b/solidity/nativecoinERC20/README.md
new file mode 100644
index 00000000..dde536fa
--- /dev/null
+++ b/solidity/nativecoinERC20/README.md
@@ -0,0 +1,33 @@
+## Set up
+Node >= 10.x
+```
+$ node --version
+v15.12.0
+```
+Install tools
+```
+$ npm install --global yarn truffle@5.3.0
+```
+Install dependencies
+```
+$ yarn
+```
+
+## Test
+1. Run in a background process or seperate terminal window
+```
+$ docker run --rm -d -p 9933:9933 -p 9944:9944 purestake/moonbeam:v0.9.2 --dev --ws-external --rpc-external
+```
+2. Compile contracts
+```
+$ yarn contract:compile
+```
+3. Run unit and integration test
+```
+$ yarn test
+```
+- Run specific test
+```
+$ yarn test:unit
+$ yarn test:integration
+```
diff --git a/solidity/nativecoinERC20/contracts/BSHCore.sol b/solidity/nativecoinERC20/contracts/BSHCore.sol
new file mode 100644
index 00000000..2c17c745
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/BSHCore.sol
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "./interfaces/IBSHPeriphery.sol";
+import "./interfaces/IBSHCore.sol";
+import "./libraries/String.sol";
+import "./libraries/Types.sol";
+import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
+import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
+
+/**
+ @title BSHCore contract
+ @dev This contract is used to handle coin transferring service
+ Note: The coin of following contract can be:
+ Native Coin : The native coin of this chain
+ Wrapped Native Coin : A tokenized ERC1155 version of another native coin like ICX
+*/
+contract BSHCore is
+ Initializable,
+ IBSHCore,
+ ERC20Upgradeable,
+ ReentrancyGuardUpgradeable
+{
+ using SafeMathUpgradeable for uint256;
+ using String for string;
+ event SetOwnership(address indexed promoter, address indexed newOwner);
+ event RemoveOwnership(address indexed remover, address indexed formerOwner);
+
+ modifier onlyOwner() {
+ require(owners[msg.sender] == true, "Unauthorized");
+ _;
+ }
+
+ modifier onlyBSHPeriphery() {
+ require(msg.sender == address(bshPeriphery), "Unauthorized");
+ _;
+ }
+
+ uint256 private constant FEE_DENOMINATOR = 10**4;
+ uint256 public feeNumerator;
+ uint256 public fixedFee;
+ uint256 private constant RC_OK = 0;
+ uint256 private constant RC_ERR = 1;
+
+ IBSHPeriphery internal bshPeriphery;
+
+ address[] private listOfOwners;
+ uint256[] private chargedAmounts; // a list of amounts have been charged so far (use this when Fee Gathering occurs)
+ string[] internal coinsName; // a string array stores names of supported coins
+ string[] private chargedCoins; // a list of coins' names have been charged so far (use this when Fee Gathering occurs)
+
+ mapping(address => bool) private owners;
+ mapping(string => uint256) internal aggregationFee; // storing Aggregation Fee in state mapping variable.
+ mapping(address => mapping(string => Types.Balance)) internal balances;
+ mapping(string => uint256) private coins; // a list of all supported coins
+
+ function initialize(
+ string calldata _nativeCoinName,
+ uint256 _feeNumerator,
+ uint256 _fixedFee,
+ string calldata _tokenName,
+ string calldata _tokenSymbol,
+ uint256 _initialSupply
+ ) public initializer {
+ __ERC20_init(_tokenName, _tokenSymbol);
+ owners[msg.sender] = true;
+ listOfOwners.push(msg.sender);
+ emit SetOwnership(address(0), msg.sender);
+
+ coins[_nativeCoinName] = 0;
+ coins[_tokenName] = 1;
+ feeNumerator = _feeNumerator;
+ fixedFee = _fixedFee;
+ coinsName.push(_nativeCoinName);
+ coinsName.push(_tokenName);
+ _mint(msg.sender, _initialSupply);
+ }
+
+ /**
+ @notice Adding another Onwer.
+ @dev Caller must be an Onwer of BTP network
+ @param _owner Address of a new Onwer.
+ */
+ function addOwner(address _owner) external override onlyOwner {
+ require(owners[_owner] == false, "ExistedOwner");
+ owners[_owner] = true;
+ listOfOwners.push(_owner);
+ emit SetOwnership(msg.sender, _owner);
+ }
+
+ /**
+ @notice Removing an existing Owner.
+ @dev Caller must be an Owner of BTP network
+ @dev If only one Owner left, unable to remove the last Owner
+ @param _owner Address of an Owner to be removed.
+ */
+ function removeOwner(address _owner) external override onlyOwner {
+ require(listOfOwners.length > 1, "Unable to remove last Owner");
+ require(owners[_owner] == true, "Removing Owner not found");
+ delete owners[_owner];
+ _remove(_owner);
+ emit RemoveOwnership(msg.sender, _owner);
+ }
+
+ function _remove(address _addr) internal {
+ for (uint256 i = 0; i < listOfOwners.length; i++)
+ if (listOfOwners[i] == _addr) {
+ listOfOwners[i] = listOfOwners[listOfOwners.length - 1];
+ listOfOwners.pop();
+ break;
+ }
+ }
+
+ /**
+ @notice Checking whether one specific address has Owner role.
+ @dev Caller can be ANY
+ @param _owner Address needs to verify.
+ */
+ function isOwner(address _owner) external view override returns (bool) {
+ return owners[_owner];
+ }
+
+ /**
+ @notice Get a list of current Owners
+ @dev Caller can be ANY
+ @return An array of addresses of current Owners
+ */
+ function getOwners() external view override returns (address[] memory) {
+ return listOfOwners;
+ }
+
+ /**
+ @notice update BSH Periphery address.
+ @dev Caller must be an Owner of this contract
+ _bshPeriphery Must be different with the existing one.
+ @param _bshPeriphery BSHPeriphery contract address.
+ */
+ function updateBSHPeriphery(address _bshPeriphery)
+ external
+ override
+ onlyOwner
+ {
+ require(_bshPeriphery != address(0), "InvalidSetting");
+ if (address(bshPeriphery) != address(0)) {
+ require(
+ bshPeriphery.hasPendingRequest() == false,
+ "HasPendingRequest"
+ );
+ }
+ bshPeriphery = IBSHPeriphery(_bshPeriphery);
+ }
+
+ /**
+ @notice set fee ratio.
+ @dev Caller must be an Owner of this contract
+ The transfer fee is calculated by feeNumerator/FEE_DEMONINATOR.
+ The feeNumetator should be less than FEE_DEMONINATOR
+ _feeNumerator is set to `10` in construction by default, which means the default fee ratio is 0.1%.
+ @param _feeNumerator the fee numerator
+ */
+ function setFeeRatio(uint256 _feeNumerator) external override onlyOwner {
+ require(_feeNumerator <= FEE_DENOMINATOR, "InvalidSetting");
+ feeNumerator = _feeNumerator;
+ }
+
+ /**
+ @notice set Fixed Fee.
+ @dev Caller must be an Owner
+ @param _fixedFee A new value of Fixed Fee
+ */
+ function setFixedFee(uint256 _fixedFee) external override onlyOwner {
+ require(_fixedFee > 0, "InvalidSetting");
+ fixedFee = _fixedFee;
+ }
+
+ /**
+ @notice Registers a wrapped coin and id number of a supporting coin.
+ @dev Caller must be an Owner of this contract
+ _name Must be different with the native coin name.
+ @dev '_id' of a wrapped coin is generated by using keccak256
+ '_id' = 0 is fixed to assign to native coin
+ @param _name Coin name.
+ */
+ function register(string calldata _name) external override onlyOwner {
+ require(coins[_name] == 0, "ExistToken");
+ coins[_name] = uint256(keccak256(abi.encodePacked(_name)));
+ coinsName.push(_name);
+ }
+
+ /**
+ @notice Return all supported coins names
+ @dev
+ @return _names An array of strings.
+ */
+ function coinNames()
+ external
+ view
+ override
+ returns (string[] memory _names)
+ {
+ return coinsName;
+ }
+
+ /**
+ @notice Check Validity of a _coinName
+ @dev Call by BSHPeriphery contract to validate a requested _coinName
+ @return _valid true of false
+ */
+ function isValidCoin(string calldata _coinName)
+ external
+ view
+ override
+ returns (bool _valid)
+ {
+ return (coins[_coinName] != 0 || _coinName.compareTo(coinsName[0]));
+ }
+
+ /**
+ @notice Return a usable/locked/refundable balance of an account based on coinName.
+ @return _usableBalance the balance that users are holding.
+ @return _lockedBalance when users transfer the coin,
+ it will be locked until getting the Service Message Response.
+ @return _refundableBalance refundable balance is the balance that will be refunded to users.
+ */
+ function getBalanceOf(address _owner, string memory _coinName)
+ external
+ view
+ override
+ returns (
+ uint256 _usableBalance,
+ uint256 _lockedBalance,
+ uint256 _refundableBalance
+ )
+ {
+ if (_coinName.compareTo(coinsName[0])) {
+ return (
+ address(_owner).balance,
+ balances[_owner][_coinName].lockedBalance,
+ balances[_owner][_coinName].refundableBalance
+ );
+ }
+ return (
+ this.balanceOf(_owner),
+ balances[_owner][_coinName].lockedBalance,
+ balances[_owner][_coinName].refundableBalance
+ );
+ }
+
+ /**
+ @notice Return a list accumulated Fees.
+ @dev only return the asset that has Asset's value greater than 0
+ @return _accumulatedFees An array of Asset
+ */
+ function getAccumulatedFees()
+ external
+ view
+ override
+ returns (Types.Asset[] memory _accumulatedFees)
+ {
+ _accumulatedFees = new Types.Asset[](coinsName.length);
+ for (uint256 i = 0; i < coinsName.length; i++) {
+ _accumulatedFees[i] = (
+ Types.Asset(coinsName[i], aggregationFee[coinsName[i]])
+ );
+ }
+ return _accumulatedFees;
+ }
+
+ /**
+ @notice Allow users to deposit `msg.value` native coin into a BSHCore contract.
+ @dev MUST specify msg.value
+ @param _to An address that a user expects to receive an amount of tokens.
+ */
+ function transferNativeCoin(string calldata _to) external payable override {
+ // Aggregation Fee will be charged on BSH Contract
+ // A new charging fee has been proposed. `fixedFee` is introduced
+ // _chargeAmt = fixedFee + msg.value * feeNumerator / FEE_DENOMINATOR
+ // Thus, it's likely that _chargeAmt is always greater than 0
+ // require(_chargeAmt > 0) can be omitted
+ // If msg.value less than _chargeAmt, it likely fails when calculating
+ // _amount = _value - _chargeAmt
+ uint256 _chargeAmt = msg
+ .value
+ .mul(feeNumerator)
+ .div(FEE_DENOMINATOR)
+ .add(fixedFee);
+
+ // @dev msg.value is an amount request to transfer (include fee)
+ // Later on, it will be calculated a true amount that should be received at a destination
+ _sendServiceMessage(
+ msg.sender,
+ _to,
+ coinsName[0],
+ msg.value,
+ _chargeAmt
+ );
+ }
+
+ /**
+ @notice Allow users to deposit an amount of wrapped native coin `_coinName` from the `msg.sender` address into the BSHCore contract.
+ @dev Caller must set to approve that the wrapped tokens can be transferred out of the `msg.sender` account by BSHCore contract.
+ It MUST revert if the balance of the holder for token `_coinName` is lower than the `_value` sent.
+ @param _coinName A given name of a wrapped coin
+ @param _value An amount request to transfer from a Requester (include fee)
+ @param _to Target BTP address.
+ */
+ function transfer(
+ string calldata _coinName,
+ uint256 _value,
+ string calldata _to
+ ) external override {
+ //TODO: check how to keep method name with overloading
+ require(this.isValidCoin(_coinName), "UnregisterCoin");
+ // _chargeAmt = fixedFee + msg.value * feeNumerator / FEE_DENOMINATOR
+ // Thus, it's likely that _chargeAmt is always greater than 0
+ // require(_chargeAmt > 0) can be omitted
+ // If _value less than _chargeAmt, it likely fails when calculating
+ // _amount = _value - _chargeAmt
+ uint256 _chargeAmt = _value.mul(feeNumerator).div(FEE_DENOMINATOR).add(
+ fixedFee
+ );
+ // Transfer and Lock Token processes:
+ // BSHCore contract calls transferFrom() to transfer the Token from Caller's account (msg.sender)
+ // Before that, Caller must approve (setApproveForAll) to accept
+ // token being transfer out by an Operator
+ // If this requirement is failed, a transaction is reverted.
+ // After transferring token, BSHCore contract updates Caller's locked balance
+ // as a record of pending transfer transaction
+ // When a transaction is completed without any error on another chain,
+ // Locked Token amount (bind to an address of caller) will be reset/subtract,
+ // then emit a successful TransferEnd event as a notification
+ // Otherwise, the locked amount will also be updated
+ // but BSHCore contract will issue a refund to Caller before emitting an error TransferEnd event
+ this.transferFrom(msg.sender, address(this), _value);
+ // @dev _value is an amount request to transfer (include fee)
+ // Later on, it will be calculated a true amount that should be received at a destination
+ _sendServiceMessage(msg.sender, _to, _coinName, _value, _chargeAmt);
+ }
+
+ /**
+ @notice This private function handles overlapping procedure before sending a service message to BSHPeriphery
+ @param _from An address of a Requester
+ @param _to BTP address of of Receiver on another chain
+ @param _coinName A given name of a requested coin
+ @param _value A requested amount to transfer from a Requester (include fee)
+ @param _chargeAmt An amount being charged for this request
+ */
+ function _sendServiceMessage(
+ address _from,
+ string calldata _to,
+ string memory _coinName,
+ uint256 _value,
+ uint256 _chargeAmt
+ ) private {
+ // Lock this requested _value as a record of a pending transferring transaction
+ // @dev `_value` is a requested amount to transfer, from a Requester, including charged fee
+ // The true amount to receive at a destination receiver is calculated by
+ // _amounts[0] = _value.sub(_chargeAmt);
+ lockBalance(_from, _coinName, _value);
+ string[] memory _coins = new string[](1);
+ _coins[0] = _coinName;
+ uint256[] memory _amounts = new uint256[](1);
+ _amounts[0] = _value.sub(_chargeAmt);
+ uint256[] memory _fees = new uint256[](1);
+ _fees[0] = _chargeAmt;
+
+ // @dev `_amounts` is a true amount to receive at a destination after deducting a charged fee
+ bshPeriphery.sendServiceMessage(_from, _to, _coins, _amounts, _fees);
+ }
+
+ /**
+ @notice Reclaim the token's refundable balance by an owner.
+ @dev Caller must be an owner of coin
+ The amount to claim must be smaller or equal than refundable balance
+ @param _coinName A given name of coin
+ @param _value An amount of re-claiming tokens
+ */
+ function reclaim(string calldata _coinName, uint256 _value)
+ external
+ override
+ nonReentrant
+ {
+ require(
+ balances[msg.sender][_coinName].refundableBalance >= _value,
+ "Imbalance"
+ );
+
+ balances[msg.sender][_coinName].refundableBalance = balances[
+ msg.sender
+ ][_coinName].refundableBalance.sub(_value);
+
+ this.refund(msg.sender, _coinName, _value);
+ }
+
+ // Solidity does not allow using try_catch with interal/private function
+ // Thus, this function would be set as 'external`
+ // But, it has restriction. It should be called by this contract only
+ // In addition, there are only two functions calling this refund()
+ // + handleRequestService(): this function only called by BSHPeriphery
+ // + reclaim(): this function can be called by ANY
+ // In case of reentrancy attacks, the chance happenning on BSHPeriphery
+ // since it requires a request from BMC which requires verification fron BMV
+ // reclaim() has higher chance to have reentrancy attacks.
+ // So, it must be prevented by adding 'nonReentrant'
+ function refund(
+ address _to,
+ string calldata _coinName,
+ uint256 _value
+ ) external {
+ require(msg.sender == address(this), "Unauthorized");
+ uint256 _id = coins[_coinName];
+ if (_id == 0) {
+ paymentTransfer(payable(_to), _value);
+ } else {
+ this.transferFrom(address(this), _to, _value);
+ }
+ }
+
+ function paymentTransfer(address payable _to, uint256 _amount) private {
+ (bool sent, ) = _to.call{value: _amount}("");
+ require(sent, "Payment transfer failed");
+ }
+
+ /**
+ @notice mint the wrapped coin.
+ @dev Caller must be an BSHPeriphery contract
+ Invalid _coinName will have an _id = 0. However, _id = 0 is also dedicated to Native Coin
+ Thus, BSHPeriphery will check a validity of a requested _coinName before calling
+ for the _coinName indicates with id = 0, it should send the Native Coin (Example: PRA) to user account
+ @param _to the account receive the minted coin
+ @param _coinName coin name
+ @param _value the minted amount
+ */
+ function mint(
+ address _to,
+ string calldata _coinName,
+ uint256 _value
+ ) external override onlyBSHPeriphery {
+ uint256 _id = coins[_coinName];
+ if (_id == 0) {
+ paymentTransfer(payable(_to), _value);
+ } else {
+ _mint(_to, _value);
+ }
+ }
+
+ /**
+ @notice Handle a response of a requested service
+ @dev Caller must be an BSHPeriphery contract
+ @param _requester An address of originator of a requested service
+ @param _coinName A name of requested coin
+ @param _value An amount to receive on a destination chain
+ @param _fee An amount of charged fee
+ */
+ function handleResponseService(
+ address _requester,
+ string calldata _coinName,
+ uint256 _value,
+ uint256 _fee,
+ uint256 _rspCode
+ ) external override onlyBSHPeriphery {
+ // Fee Gathering and Transfer Coin Request use the same method
+ // and both have the same response
+ // In case of Fee Gathering's response, `_requester` is this contract's address
+ // Thus, check that first
+ // -- If `_requester` is this contract's address, then check whethere response's code is RC_ERR
+ // In case of RC_ERR, adding back charged fees to `aggregationFee` state variable
+ // In case of RC_OK, ignore and return
+ // -- Otherwise, handle service's response as normal
+ if (_requester == address(this)) {
+ if (_rspCode == RC_ERR) {
+ aggregationFee[_coinName] = aggregationFee[_coinName].add(
+ _value
+ );
+ }
+ return;
+ }
+ uint256 _amount = _value.add(_fee);
+ balances[_requester][_coinName].lockedBalance = balances[_requester][
+ _coinName
+ ].lockedBalance.sub(_amount);
+
+ // A new implementation has been proposed to prevent spam attacks
+ // In receiving error response, BSHCore refunds `_value`, not including `_fee`, back to Requestor
+ if (_rspCode == RC_ERR) {
+ try this.refund(_requester, _coinName, _value) {} catch {
+ balances[_requester][_coinName].refundableBalance = balances[
+ _requester
+ ][_coinName].refundableBalance.add(_value);
+ }
+ } else if (_rspCode == RC_OK) {
+ uint256 _id = coins[_coinName];
+ if (_id != 0) {
+ _burn(address(this), _value);
+ }
+ }
+ aggregationFee[_coinName] = aggregationFee[_coinName].add(_fee);
+ }
+
+ /**
+ @notice Handle a request of Fee Gathering
+ Usage: Copy all charged fees to an array
+ @dev Caller must be an BSHPeriphery contract
+ */
+ function transferFees(string calldata _fa)
+ external
+ override
+ onlyBSHPeriphery
+ {
+ // @dev Due to uncertainty in identifying a size of returning memory array
+ // and Solidity does not allow to use 'push' with memory array (only storage)
+ // thus, must use 'temp' storage state variable
+ for (uint256 i = 0; i < coinsName.length; i++) {
+ if (aggregationFee[coinsName[i]] != 0) {
+ chargedCoins.push(coinsName[i]);
+ chargedAmounts.push(aggregationFee[coinsName[i]]);
+ delete aggregationFee[coinsName[i]];
+ }
+ }
+ bshPeriphery.sendServiceMessage(
+ address(this),
+ _fa,
+ chargedCoins,
+ chargedAmounts,
+ new uint256[](chargedCoins.length) // chargedFees is an array of 0 since this is a fee gathering request
+ );
+ delete chargedCoins;
+ delete chargedAmounts;
+ }
+
+ function lockBalance(
+ address _to,
+ string memory _coinName,
+ uint256 _value
+ ) private {
+ balances[_to][_coinName].lockedBalance = balances[_to][_coinName]
+ .lockedBalance
+ .add(_value);
+ }
+}
\ No newline at end of file
diff --git a/solidity/nativecoinERC20/contracts/BSHPeriphery.sol b/solidity/nativecoinERC20/contracts/BSHPeriphery.sol
new file mode 100644
index 00000000..78639287
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/BSHPeriphery.sol
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "./interfaces/IBSHPeriphery.sol";
+import "./interfaces/IBSHCore.sol";
+import "./interfaces/IBMCPeriphery.sol";
+import "./libraries/Types.sol";
+import "./libraries/RLPEncodeStruct.sol";
+import "./libraries/RLPDecodeStruct.sol";
+import "./libraries/ParseAddress.sol";
+import "./libraries/String.sol";
+import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
+
+/**
+ @title BSHPeriphery contract
+ @dev This contract is used to handle communications among BMCService and BSHCore contract
+ @dev OwnerUpgradeable has been removed. This contract does not have its own Owners
+ Instead, BSHCore manages ownership roles.
+ Thus, BSHPeriphery should call bshCore.isOwner() and pass an address for verification
+ in case of implementing restrictions, if needed, in the future.
+*/
+contract BSHPeriphery is Initializable, IBSHPeriphery {
+ using RLPEncodeStruct for Types.TransferCoin;
+ using RLPEncodeStruct for Types.ServiceMessage;
+ using RLPEncodeStruct for Types.Response;
+ using RLPDecodeStruct for bytes;
+ using SafeMathUpgradeable for uint256;
+ using ParseAddress for address;
+ using ParseAddress for string;
+ using String for string;
+ using String for uint256;
+
+ /** @notice Sends a receipt to user
+ The `_from` sender
+ The `_to` receiver.
+ The `_sn` sequence number of service message.
+ The `_assetDetails` a list of `_coinName` and `_value`
+ */
+ event TransferStart(
+ address indexed _from,
+ string _to,
+ uint256 _sn,
+ Types.AssetTransferDetail[] _assetDetails
+ );
+
+ /** @notice Sends a final notification to a user
+ The `_from` sender
+ The `_sn` sequence number of service message.
+ The `_code` response code, i.e. RC_OK = 0, RC_ERR = 1
+ The `_response` message of response if error
+ */
+ event TransferEnd(
+ address indexed _from,
+ uint256 _sn,
+ uint256 _code,
+ string _response
+ );
+
+ /** @notice Notify that BSH contract has received unknown response
+ The `_from` sender
+ The `_sn` sequence number of service message
+ */
+ event UnknownResponse(string _from, uint256 _sn);
+
+ IBMCPeriphery private bmc;
+ IBSHCore internal bshCore;
+ mapping(uint256 => Types.PendingTransferCoin) public requests; // a list of transferring requests
+ string public serviceName; // BSH Service Name
+
+ uint256 private constant RC_OK = 0;
+ uint256 private constant RC_ERR = 1;
+ uint256 private serialNo; // a counter of sequence number of service message
+ uint256 private numOfPendingRequests;
+
+ modifier onlyBMC {
+ require(msg.sender == address(bmc), "Unauthorized");
+ _;
+ }
+
+ function initialize(
+ address _bmc,
+ address _bshCore,
+ string memory _serviceName
+ ) public initializer {
+ bmc = IBMCPeriphery(_bmc);
+ bshCore = IBSHCore(_bshCore);
+ serviceName = _serviceName;
+ }
+
+ /**
+ @notice Check whether BSHPeriphery has any pending transferring requests
+ @return true or false
+ */
+ function hasPendingRequest() external view override returns (bool) {
+ return numOfPendingRequests != 0;
+ }
+
+ function sendServiceMessage(
+ address _from,
+ string memory _to,
+ string[] memory _coinNames,
+ uint256[] memory _values,
+ uint256[] memory _fees
+ ) external override {
+ // Send Service Message to BMC
+ // If '_to' address is an invalid BTP Address format
+ // VM throws an error and revert(). Thus, it does not need
+ // a try_catch at this point
+ (string memory _toNetwork, string memory _toAddress) =
+ _to.splitBTPAddress();
+ Types.Asset[] memory _assets = new Types.Asset[](_coinNames.length);
+ Types.AssetTransferDetail[] memory _assetDetails =
+ new Types.AssetTransferDetail[](_coinNames.length);
+ for (uint256 i = 0; i < _coinNames.length; i++) {
+ _assets[i] = Types.Asset(_coinNames[i], _values[i]);
+ _assetDetails[i] = Types.AssetTransferDetail(
+ _coinNames[i],
+ _values[i],
+ _fees[i]
+ );
+ }
+
+ serialNo++;
+
+ // Because `stack is too deep`, must create `_strFrom` to waive this error
+ // `_strFrom` is a string type of an address `_from`
+ string memory _strFrom = _from.toString();
+ bmc.sendMessage(
+ _toNetwork,
+ serviceName,
+ serialNo,
+ Types
+ .ServiceMessage(
+ Types
+ .ServiceType
+ .REQUEST_COIN_TRANSFER,
+ Types
+ .TransferCoin(_strFrom, _toAddress, _assets)
+ .encodeTransferCoinMsg()
+ )
+ .encodeServiceMessage()
+ );
+ // Push pending tx into Record list
+ requests[serialNo] = Types.PendingTransferCoin(
+ _strFrom,
+ _to,
+ _coinNames,
+ _values,
+ _fees
+ );
+ numOfPendingRequests++;
+ emit TransferStart(_from, _to, serialNo, _assetDetails);
+ }
+
+ /**
+ @notice BSH handle BTP Message from BMC contract
+ @dev Caller must be BMC contract only
+ @param _from An originated network address of a request
+ @param _svc A service name of BSH contract
+ @param _sn A serial number of a service request
+ @param _msg An RLP message of a service request/service response
+ */
+ function handleBTPMessage(
+ string calldata _from,
+ string calldata _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external override onlyBMC {
+ require(_svc.compareTo(serviceName) == true, "InvalidSvc");
+ Types.ServiceMessage memory _sm = _msg.decodeServiceMessage();
+ string memory errMsg;
+
+ if (_sm.serviceType == Types.ServiceType.REQUEST_COIN_TRANSFER) {
+ Types.TransferCoin memory _tc = _sm.data.decodeTransferCoinMsg();
+ // checking receiving address whether is a valid address
+ // revert() if not a valid one
+ try this.checkParseAddress(_tc.to) {
+ try this.handleRequestService(_tc.to, _tc.assets) {
+ sendResponseMessage(
+ Types.ServiceType.REPONSE_HANDLE_SERVICE,
+ _from,
+ _sn,
+ "",
+ RC_OK
+ );
+ return;
+ } catch Error(string memory _err) {
+ errMsg = _err;
+ }
+ } catch {
+ errMsg = "InvalidAddress";
+ }
+ sendResponseMessage(
+ Types.ServiceType.REPONSE_HANDLE_SERVICE,
+ _from,
+ _sn,
+ errMsg,
+ RC_ERR
+ );
+ } else if (
+ _sm.serviceType == Types.ServiceType.REPONSE_HANDLE_SERVICE
+ ) {
+ // Check whether '_sn' is pending state
+ require(bytes(requests[_sn].from).length != 0, "InvalidSN");
+ Types.Response memory response = _sm.data.decodeResponse();
+ // @dev Not implement try_catch at this point
+ // + If RESPONSE_REQUEST_SERVICE:
+ // If RC_ERR, BSHCore proceeds a refund. If a refund is failed, BSHCore issues refundable Balance
+ // If RC_OK:
+ // - requested coin = native -> update aggregation fee (likely no issue)
+ // - requested coin = wrapped coin -> BSHCore calls itself to burn its tokens and update aggregation fee (likely no issue)
+ // The only issue, which might happen, is BSHCore's token balance lower than burning amount
+ // If so, there might be something went wrong before
+ // + If RESPONSE_FEE_GATHERING
+ // If RC_ERR, BSHCore saves charged fees back to `aggregationFee` state mapping variable
+ // If RC_OK: do nothing
+ handleResponseService(_sn, response.code, response.message);
+ } else if (_sm.serviceType == Types.ServiceType.UNKNOWN_TYPE) {
+ emit UnknownResponse(_from, _sn);
+ } else {
+ // If none of those types above, BSH responds a message of RES_UNKNOWN_TYPE
+ sendResponseMessage(
+ Types.ServiceType.UNKNOWN_TYPE,
+ _from,
+ _sn,
+ "Unknown",
+ RC_ERR
+ );
+ }
+ }
+
+ /**
+ @notice BSH handle BTP Error from BMC contract
+ @dev Caller must be BMC contract only
+ @param _svc A service name of BSH contract
+ @param _sn A serial number of a service request
+ @param _code A response code of a message (RC_OK / RC_ERR)
+ @param _msg A response message
+ */
+ function handleBTPError(
+ string calldata, /* _src */
+ string calldata _svc,
+ uint256 _sn,
+ uint256 _code,
+ string calldata _msg
+ ) external override onlyBMC {
+ require(_svc.compareTo(serviceName) == true, "InvalidSvc");
+ require(bytes(requests[_sn].from).length != 0, "InvalidSN");
+ string memory _emitMsg =
+ string("errCode: ")
+ .concat(_code.toString())
+ .concat(", errMsg: ")
+ .concat(_msg);
+ handleResponseService(_sn, RC_ERR, _emitMsg);
+ }
+
+ function handleResponseService(
+ uint256 _sn,
+ uint256 _code,
+ string memory _msg
+ ) private {
+ address _caller = requests[_sn].from.parseAddress();
+ uint256 loop = requests[_sn].coinNames.length;
+ for (uint256 i = 0; i < loop; i++) {
+ bshCore.handleResponseService(
+ _caller,
+ requests[_sn].coinNames[i],
+ requests[_sn].amounts[i],
+ requests[_sn].fees[i],
+ _code
+ );
+ }
+ delete requests[_sn];
+ numOfPendingRequests--;
+ emit TransferEnd(_caller, _sn, _code, _msg);
+ }
+
+ /**
+ @notice Handle a list of minting/transferring coins/tokens
+ @dev Caller must be BMC contract only
+ @param _to An address to receive coins/tokens
+ @param _assets A list of requested coin respectively with an amount
+ */
+ function handleRequestService(
+ string memory _to,
+ Types.Asset[] memory _assets
+ ) external {
+ require(msg.sender == address(this), "Unauthorized");
+ for (uint256 i = 0; i < _assets.length; i++) {
+ require(
+ bshCore.isValidCoin(_assets[i].coinName) == true,
+ "UnregisteredCoin"
+ );
+ // @dev There might be many errors generating by BSHCore contract
+ // which includes also low-level error
+ // Thus, must use try_catch at this point so that it can return an expected response
+ try
+ bshCore.mint(
+ _to.parseAddress(),
+ _assets[i].coinName,
+ _assets[i].value
+ )
+ {} catch {
+ revert("TransferFailed");
+ }
+ }
+ }
+
+ function sendResponseMessage(
+ Types.ServiceType _serviceType,
+ string memory _to,
+ uint256 _sn,
+ string memory _msg,
+ uint256 _code
+ ) private {
+ bmc.sendMessage(
+ _to,
+ serviceName,
+ _sn,
+ Types
+ .ServiceMessage(
+ _serviceType,
+ Types.Response(_code, _msg).encodeResponse()
+ )
+ .encodeServiceMessage()
+ );
+ }
+
+ /**
+ @notice BSH handle Gather Fee Message request from BMC contract
+ @dev Caller must be BMC contract only
+ @param _fa A BTP address of fee aggregator
+ @param _svc A name of the service
+ */
+ function handleFeeGathering(string calldata _fa, string calldata _svc)
+ external
+ override
+ onlyBMC
+ {
+ require(_svc.compareTo(serviceName) == true, "InvalidSvc");
+ // If adress of Fee Aggregator (_fa) is invalid BTP address format
+ // revert(). Then, BMC will catch this error
+ // @dev this part simply check whether `_fa` is splittable (`prefix` + `_net` + `dstAddr`)
+ // checking validity of `_net` and `dstAddr` does not belong to BSHPeriphery's scope
+ _fa.splitBTPAddress();
+ bshCore.transferFees(_fa);
+ }
+
+ // @dev Solidity does not allow to use try_catch with internal function
+ // Thus, this is a work-around solution
+ // Since this function is basically checking whether a string address
+ // can be parsed to address type. Hence, it would not have any restrictions
+ function checkParseAddress(string calldata _to) external pure {
+ _to.parseAddress();
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/interfaces/IBMCPeriphery.sol b/solidity/nativecoinERC20/contracts/interfaces/IBMCPeriphery.sol
new file mode 100644
index 00000000..78787903
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/interfaces/IBMCPeriphery.sol
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "../libraries/Types.sol";
+
+interface IBMCPeriphery {
+ /**
+ @notice Get BMC BTP address
+ */
+ function getBmcBtpAddress() external view returns (string memory);
+
+ /**
+ @notice Verify and decode RelayMessage with BMV, and dispatch BTP Messages to registered BSHs
+ @dev Caller must be a registered relayer.
+ @param _prev BTP Address of the BMC generates the message
+ @param _msg base64 encoded string of serialized bytes of Relay Message refer RelayMessage structure
+ */
+ function handleRelayMessage(string calldata _prev, string calldata _msg)
+ external;
+
+ /**
+ @notice Send the message to a specific network.
+ @dev Caller must be an registered BSH.
+ @param _to Network Address of destination network
+ @param _svc Name of the service
+ @param _sn Serial number of the message, it should be positive
+ @param _msg Serialized bytes of Service Message
+ */
+ function sendMessage(
+ string calldata _to,
+ string calldata _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external;
+
+ /*
+ @notice Get status of BMC.
+ @param _link BTP Address of the connected BMC
+ @return _linkStats The link status
+ */
+ function getStatus(string calldata _link)
+ external
+ view
+ returns (Types.LinkStats memory _linkStats);
+}
diff --git a/solidity/nativecoinERC20/contracts/interfaces/IBMV.sol b/solidity/nativecoinERC20/contracts/interfaces/IBMV.sol
new file mode 100644
index 00000000..e370af4e
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/interfaces/IBMV.sol
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+interface IBMV {
+ /**
+ @return base64EncodedMTA Base64 encode of Merkle Tree
+ */
+ function getMTA() external view returns (string memory base64EncodedMTA);
+
+ /**
+ @return addr connected BMC address
+ */
+ function getConnectedBMC() external view returns (address addr);
+
+ /**
+ @return net network address of the blockchain
+ */
+ function getNetAddress() external view returns (string memory net);
+
+ /**
+ @return serializedHash hash of RLP encode from given list of validators
+ @return addresses list of validators' addresses
+ */
+ function getValidators()
+ external
+ view
+ returns (bytes32 serializedHash, address[] memory addresses);
+
+ /**
+ @notice Used by the relay to resolve next BTP Message to send.
+ Called by BMC.
+ @return height height of MerkleTreeAccumulator
+ @return offset offset of MerkleTreeAccumulator
+ @return lastHeight block height of last relayed BTP Message
+ */
+ function getStatus()
+ external
+ view
+ returns (
+ uint256 height,
+ uint256 offset,
+ uint256 lastHeight
+ );
+
+ /**
+ @notice Decodes Relay Messages and process BTP Messages.
+ If there is an error, then it sends a BTP Message containing the Error Message.
+ BTP Messages with old sequence numbers are ignored. A BTP Message contains future sequence number will fail.
+ @param _bmc BTP Address of the BMC handling the message
+ @param _prev BTP Address of the previous BMC
+ @param _seq next sequence number to get a message
+ @param _msg serialized bytes of Relay Message
+ @return serializedMessages List of serialized bytes of a BTP Message
+ */
+ function handleRelayMessage(
+ string calldata _bmc,
+ string calldata _prev,
+ uint256 _seq,
+ string calldata _msg
+ ) external returns (bytes[] memory serializedMessages);
+}
diff --git a/solidity/nativecoinERC20/contracts/interfaces/IBSH.sol b/solidity/nativecoinERC20/contracts/interfaces/IBSH.sol
new file mode 100644
index 00000000..26769f6e
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/interfaces/IBSH.sol
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+interface IBSH {
+ /**
+ @notice BSH handle BTP Message from BMC contract
+ @dev Caller must be BMC contract only
+ @param _from An originated network address of a request
+ @param _svc A service name of BSH contract
+ @param _sn A serial number of a service request
+ @param _msg An RLP message of a service request/service response
+ */
+ function handleBTPMessage(
+ string calldata _from,
+ string calldata _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external;
+
+ /**
+ @notice BSH handle BTP Error from BMC contract
+ @dev Caller must be BMC contract only
+ @param _svc A service name of BSH contract
+ @param _sn A serial number of a service request
+ @param _code A response code of a message (RC_OK / RC_ERR)
+ @param _msg A response message
+ */
+ function handleBTPError(
+ string calldata _src,
+ string calldata _svc,
+ uint256 _sn,
+ uint256 _code,
+ string calldata _msg
+ ) external;
+
+ /**
+ @notice BSH handle Gather Fee Message request from BMC contract
+ @dev Caller must be BMC contract only
+ @param _fa A BTP address of fee aggregator
+ @param _svc A name of the service
+ */
+ function handleFeeGathering(string calldata _fa, string calldata _svc)
+ external;
+}
diff --git a/solidity/nativecoinERC20/contracts/interfaces/IBSHCore.sol b/solidity/nativecoinERC20/contracts/interfaces/IBSHCore.sol
new file mode 100644
index 00000000..770e37ee
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/interfaces/IBSHCore.sol
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "../libraries/Types.sol";
+
+/**
+ @title Interface of BSHCore contract
+ @dev This contract is used to handle coin transferring service
+ Note: The coin of following interface can be:
+ Native Coin : The native coin of this chain
+ Wrapped Native Coin : A tokenized ERC1155 version of another native coin like ICX
+*/
+interface IBSHCore{
+ /**
+ @notice Adding another Onwer.
+ @dev Caller must be an Onwer of BTP network
+ @param _owner Address of a new Onwer.
+ */
+ function addOwner(address _owner) external;
+
+ /**
+ @notice Removing an existing Owner.
+ @dev Caller must be an Owner of BTP network
+ @dev If only one Owner left, unable to remove the last Owner
+ @param _owner Address of an Owner to be removed.
+ */
+ function removeOwner(address _owner) external;
+
+ /**
+ @notice Checking whether one specific address has Owner role.
+ @dev Caller can be ANY
+ @param _owner Address needs to verify.
+ */
+ function isOwner(address _owner) external view returns (bool);
+
+ /**
+ @notice Get a list of current Owners
+ @dev Caller can be ANY
+ @return An array of addresses of current Owners
+ */
+
+ function getOwners() external view returns (address[] memory);
+
+ /**
+ @notice update BSH Periphery address.
+ @dev Caller must be an Owner of this contract
+ _bshPeriphery Must be different with the existing one.
+ @param _bshPeriphery BSHPeriphery contract address.
+ */
+ function updateBSHPeriphery(address _bshPeriphery) external;
+
+
+ /**
+ @notice set fee ratio.
+ @dev Caller must be an Owner of this contract
+ The transfer fee is calculated by feeNumerator/FEE_DEMONINATOR.
+ The feeNumetator should be less than FEE_DEMONINATOR
+ _feeNumerator is set to `10` in construction by default, which means the default fee ratio is 0.1%.
+ @param _feeNumerator the fee numerator
+ */
+ function setFeeRatio(uint256 _feeNumerator) external;
+
+ /**
+ @notice set Fixed Fee.
+ @dev Caller must be an Owner
+ @param _fixedFee A new value of Fixed Fee
+ */
+ function setFixedFee(uint256 _fixedFee) external;
+
+ /**
+ @notice Registers a wrapped coin and id number of a supporting coin.
+ @dev Caller must be an Owner of this contract
+ _name Must be different with the native coin name.
+ @dev '_id' of a wrapped coin is generated by using keccak256
+ '_id' = 0 is fixed to assign to native coin
+ @param _name Coin name.
+ */
+ function register(string calldata _name) external;
+
+ /**
+ @notice Return all supported coins names
+ @dev
+ @return _names An array of strings.
+ */
+ function coinNames() external view returns (string[] memory _names);
+
+ /**
+ @notice Check Validity of a _coinName
+ @dev Call by BSHPeriphery contract to validate a requested _coinName
+ @return _valid true of false
+ */
+ function isValidCoin(string calldata _coinName)
+ external
+ view
+ returns (bool _valid);
+
+ /**
+ @notice Return a usable/locked/refundable balance of an account based on coinName.
+ @return _usableBalance the balance that users are holding.
+ @return _lockedBalance when users transfer the coin,
+ it will be locked until getting the Service Message Response.
+ @return _refundableBalance refundable balance is the balance that will be refunded to users.
+ */
+ function getBalanceOf(address _owner, string memory _coinName)
+ external
+ view
+ returns (
+ uint256 _usableBalance,
+ uint256 _lockedBalance,
+ uint256 _refundableBalance
+ );
+
+ /**
+ @notice Return a list accumulated Fees.
+ @dev only return the asset that has Asset's value greater than 0
+ @return _accumulatedFees An array of Asset
+ */
+ function getAccumulatedFees()
+ external
+ view
+ returns (Types.Asset[] memory _accumulatedFees);
+
+ /**
+ @notice Allow users to deposit `msg.value` native coin into a BSHCore contract.
+ @dev MUST specify msg.value
+ @param _to An address that a user expects to receive an amount of tokens.
+ */
+ function transferNativeCoin(string calldata _to) external payable;
+
+ /**
+ @notice Allow users to deposit an amount of wrapped native coin `_coinName` from the `msg.sender` address into the BSHCore contract.
+ @dev Caller must set to approve that the wrapped tokens can be transferred out of the `msg.sender` account by BSHCore contract.
+ It MUST revert if the balance of the holder for token `_coinName` is lower than the `_value` sent.
+ @param _coinName A given name of a wrapped coin
+ @param _value An amount request to transfer.
+ @param _to Target BTP address.
+ */
+ function transfer(
+ string calldata _coinName,
+ uint256 _value,
+ string calldata _to
+ ) external;
+
+ /**
+ @notice Reclaim the token's refundable balance by an owner.
+ @dev Caller must be an owner of coin
+ The amount to claim must be smaller or equal than refundable balance
+ @param _coinName A given name of coin
+ @param _value An amount of re-claiming tokens
+ */
+ function reclaim(string calldata _coinName, uint256 _value) external;
+
+ /**
+ @notice mint the wrapped coin.
+ @dev Caller must be an BSHPeriphery contract
+ Invalid _coinName will have an _id = 0. However, _id = 0 is also dedicated to Native Coin
+ Thus, BSHPeriphery will check a validity of a requested _coinName before calling
+ for the _coinName indicates with id = 0, it should send the Native Coin (Example: PRA) to user account
+ @param _to the account receive the minted coin
+ @param _coinName coin name
+ @param _value the minted amount
+ */
+ function mint(
+ address _to,
+ string calldata _coinName,
+ uint256 _value
+ ) external;
+
+ /**
+ @notice Handle a request of Fee Gathering
+ @dev Caller must be an BSHPeriphery contract
+ @param _fa BTP Address of Fee Aggregator
+ */
+ function transferFees(string calldata _fa) external;
+
+ /**
+ @notice Handle a response of a requested service
+ @dev Caller must be an BSHPeriphery contract
+ @param _requester An address of originator of a requested service
+ @param _coinName A name of requested coin
+ @param _value An amount to receive on a destination chain
+ @param _fee An amount of charged fee
+ */
+ function handleResponseService(
+ address _requester,
+ string calldata _coinName,
+ uint256 _value,
+ uint256 _fee,
+ uint256 _rspCode
+ ) external;
+}
diff --git a/solidity/nativecoinERC20/contracts/interfaces/IBSHPeriphery.sol b/solidity/nativecoinERC20/contracts/interfaces/IBSHPeriphery.sol
new file mode 100644
index 00000000..7fbe4534
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/interfaces/IBSHPeriphery.sol
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./IBSH.sol";
+
+/**
+ @title Interface of BSHPeriphery contract
+ @dev This contract is used to handle communications among BMCService and BSHCore contract
+*/
+interface IBSHPeriphery is IBSH {
+ /**
+ @notice Check whether BSHPeriphery has any pending transferring requests
+ @return true or false
+ */
+ function hasPendingRequest() external view returns (bool);
+
+ /**
+ @notice Send Service Message from BSHCore contract to BMCService contract
+ @dev Caller must be BSHCore only
+ @param _to A network address of destination chain
+ @param _coinNames A list of coin name that are requested to transfer
+ @param _values A list of an amount to receive at destination chain respectively with its coin name
+ @param _fees A list of an amount of charging fee respectively with its coin name
+ */
+ function sendServiceMessage(
+ address _from,
+ string calldata _to,
+ string[] memory _coinNames,
+ uint256[] memory _values,
+ uint256[] memory _fees
+ ) external;
+
+ /**
+ @notice BSH handle BTP Message from BMC contract
+ @dev Caller must be BMC contract only
+ @param _from An originated network address of a request
+ @param _svc A service name of BSHPeriphery contract
+ @param _sn A serial number of a service request
+ @param _msg An RLP message of a service request/service response
+ */
+ function handleBTPMessage(
+ string calldata _from,
+ string calldata _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external override;
+
+ /**
+ @notice BSH handle BTP Error from BMC contract
+ @dev Caller must be BMC contract only
+ @param _svc A service name of BSHPeriphery contract
+ @param _sn A serial number of a service request
+ @param _code A response code of a message (RC_OK / RC_ERR)
+ @param _msg A response message
+ */
+ function handleBTPError(
+ string calldata _src,
+ string calldata _svc,
+ uint256 _sn,
+ uint256 _code,
+ string calldata _msg
+ ) external override;
+
+ /**
+ @notice BSH handle Gather Fee Message request from BMC contract
+ @dev Caller must be BMC contract only
+ @param _fa A BTP address of fee aggregator
+ @param _svc A name of the service
+ */
+ function handleFeeGathering(string calldata _fa, string calldata _svc)
+ external
+ override;
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/DecodeBase64.sol b/solidity/nativecoinERC20/contracts/libraries/DecodeBase64.sol
new file mode 100644
index 00000000..283540d5
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/DecodeBase64.sol
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/**
+ * @title DecodeBase64
+ * @dev A simple Base64 decoding library.
+ * @author Quang Tran
+ */
+
+library DecodeBase64 {
+ function decode(string memory _str) internal pure returns (bytes memory) {
+ bytes memory _bs = bytes(_str);
+ uint256 remove = 0;
+ if (_bs[_bs.length - 1] == "=" && _bs[_bs.length - 2] == "=") {
+ remove += 2;
+ } else if (_bs[_bs.length - 1] == "=") {
+ remove++;
+ }
+ uint256 resultLength = (_bs.length / 4) * 3 - remove;
+ bytes memory result = new bytes(resultLength);
+
+ uint256 i = 0;
+ uint256 j = 0;
+ for (; i + 4 < _bs.length; i += 4) {
+ (result[j], result[j + 1], result[j + 2]) = decode4(
+ mapBase64Char(_bs[i]),
+ mapBase64Char(_bs[i + 1]),
+ mapBase64Char(_bs[i + 2]),
+ mapBase64Char(_bs[i + 3])
+ );
+ j += 3;
+ }
+ if (remove == 1) {
+ (result[j], result[j + 1], ) = decode4(
+ mapBase64Char(_bs[_bs.length - 4]),
+ mapBase64Char(_bs[_bs.length - 3]),
+ mapBase64Char(_bs[_bs.length - 2]),
+ 0
+ );
+ } else if (remove == 2) {
+ (result[j], , ) = decode4(
+ mapBase64Char(_bs[_bs.length - 4]),
+ mapBase64Char(_bs[_bs.length - 3]),
+ 0,
+ 0
+ );
+ } else {
+ (result[j], result[j + 1], result[j + 2]) = decode4(
+ mapBase64Char(_bs[_bs.length - 4]),
+ mapBase64Char(_bs[_bs.length - 3]),
+ mapBase64Char(_bs[_bs.length - 2]),
+ mapBase64Char(_bs[_bs.length - 1])
+ );
+ }
+ return result;
+ }
+
+ function mapBase64Char(bytes1 _char) private pure returns (uint8) {
+ // solhint-disable-next-line
+ uint8 A = 0;
+ uint8 a = 26;
+ uint8 zero = 52;
+ if (uint8(_char) == 45) {
+ return 62;
+ } else if (uint8(_char) == 95) {
+ return 63;
+ } else if (uint8(_char) >= 48 && uint8(_char) <= 57) {
+ return zero + (uint8(_char) - 48);
+ } else if (uint8(_char) >= 65 && uint8(_char) <= 90) {
+ return A + (uint8(_char) - 65);
+ } else if (uint8(_char) >= 97 && uint8(_char) <= 122) {
+ return a + (uint8(_char) - 97);
+ }
+ return 0;
+ }
+
+ function decode4(
+ uint256 a0,
+ uint256 a1,
+ uint256 a2,
+ uint256 a3
+ )
+ private
+ pure
+ returns (
+ bytes1,
+ bytes1,
+ bytes1
+ )
+ {
+ uint256 n =
+ ((a0 & 63) << 18) |
+ ((a1 & 63) << 12) |
+ ((a2 & 63) << 6) |
+ (a3 & 63);
+ uint256 b0 = (n >> 16) & 255;
+ uint256 b1 = (n >> 8) & 255;
+ uint256 b2 = (n) & 255;
+ return (bytes1(uint8(b0)), bytes1(uint8(b1)), bytes1(uint8(b2)));
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/EncodeBase64.sol b/solidity/nativecoinERC20/contracts/libraries/EncodeBase64.sol
new file mode 100644
index 00000000..5a09250f
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/EncodeBase64.sol
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/**
+ * @title EncodeBase64
+ * @dev A simple Base64 encoding library.
+ * The original library was modified to make it work for our case
+ * For more info, please check the link: https://github.com/OpenZeppelin/solidity-jwt.git
+ */
+library EncodeBase64 {
+ bytes private constant BASE64URLCHARS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+ function encode(bytes memory _bs) internal pure returns (string memory) {
+ // bytes memory _bs = bytes(_str);
+ uint256 rem = _bs.length % 3;
+ uint256 resLength;
+ if (_bs.length % 3 != 0) {
+ resLength = (_bs.length / 3) * 4 + 4;
+ } else {
+ resLength = (_bs.length / 3) * 4;
+ }
+
+ // uint256 res_length = (_bs.length + 2) / 3 * 4 - ((3 - rem) % 3);
+ bytes memory res = new bytes(resLength);
+ uint256 i = 0;
+ uint256 j = 0;
+
+ for (; i + 3 <= _bs.length; i += 3) {
+ (res[j], res[j + 1], res[j + 2], res[j + 3]) = encode3(
+ uint8(_bs[i]),
+ uint8(_bs[i + 1]),
+ uint8(_bs[i + 2])
+ );
+ j += 4;
+ }
+
+ if (rem != 0) {
+ uint8 la0 = uint8(_bs[_bs.length - rem]);
+ uint8 la1 = 0;
+ if (rem == 2) {
+ la1 = uint8(_bs[_bs.length - 1]);
+ }
+ (bytes1 b0, bytes1 b1, bytes1 b2, ) = encode3(la0, la1, 0);
+ res[j] = b0;
+ res[j + 1] = b1;
+ if (rem == 1) {
+ res[j + 2] = "=";
+ res[j + 3] = "=";
+ } else if (rem == 2) {
+ res[j + 2] = b2;
+ res[j + 3] = "=";
+ }
+ }
+ return string(res);
+ }
+
+ function encode3(
+ uint256 a0,
+ uint256 a1,
+ uint256 a2
+ )
+ private
+ pure
+ returns (
+ bytes1 b0,
+ bytes1 b1,
+ bytes1 b2,
+ bytes1 b3
+ )
+ {
+ uint256 n = (a0 << 16) | (a1 << 8) | a2;
+ uint256 c0 = (n >> 18) & 63;
+ uint256 c1 = (n >> 12) & 63;
+ uint256 c2 = (n >> 6) & 63;
+ uint256 c3 = (n) & 63;
+ b0 = BASE64URLCHARS[c0];
+ b1 = BASE64URLCHARS[c1];
+ b2 = BASE64URLCHARS[c2];
+ b3 = BASE64URLCHARS[c3];
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/ParseAddress.sol b/solidity/nativecoinERC20/contracts/libraries/ParseAddress.sol
new file mode 100644
index 00000000..59475be0
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/ParseAddress.sol
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/*
+ * Utility library of inline functions on addresses
+ */
+library ParseAddress {
+ function parseAddress(string memory account)
+ internal
+ pure
+ returns (address accountAddress)
+ {
+ bytes memory accountBytes = bytes(account);
+ require(
+ accountBytes.length == 42 &&
+ accountBytes[0] == bytes1("0") &&
+ accountBytes[1] == bytes1("x"),
+ "Invalid address format"
+ );
+
+ // create a new fixed-size byte array for the ascii bytes of the address.
+ bytes memory accountAddressBytes = new bytes(20);
+
+ // declare variable types.
+ uint8 b;
+ uint8 nibble;
+ uint8 asciiOffset;
+
+ for (uint256 i = 0; i < 40; i++) {
+ // get the byte in question.
+ b = uint8(accountBytes[i + 2]);
+
+ bool isValidASCII = true;
+ // ensure that the byte is a valid ascii character (0-9, A-F, a-f)
+ if (b < 48) isValidASCII = false;
+ if (57 < b && b < 65) isValidASCII = false;
+ if (70 < b && b < 97) isValidASCII = false;
+ if (102 < b) isValidASCII = false; //bytes(hex"");
+
+ // If string contains invalid ASCII characters, revert()
+ if (!isValidASCII) revert("Invalid address");
+
+ // find the offset from ascii encoding to the nibble representation.
+ if (b < 65) {
+ // 0-9
+ asciiOffset = 48;
+ } else if (70 < b) {
+ // a-f
+ asciiOffset = 87;
+ } else {
+ // A-F
+ asciiOffset = 55;
+ }
+
+ // store left nibble on even iterations, then store byte on odd ones.
+ if (i % 2 == 0) {
+ nibble = b - asciiOffset;
+ } else {
+ accountAddressBytes[(i - 1) / 2] = (
+ byte(16 * nibble + (b - asciiOffset))
+ );
+ }
+ }
+
+ // pack up the fixed-size byte array and cast it to accountAddress.
+ bytes memory packed = abi.encodePacked(accountAddressBytes);
+ assembly {
+ accountAddress := mload(add(packed, 20))
+ }
+
+ // return false in the event the account conversion returned null address.
+ if (accountAddress == address(0)) {
+ // ensure that provided address is not also the null address first.
+ for (uint256 i = 2; i < accountBytes.length; i++)
+ require(accountBytes[i] == hex"30", "Invalid address");
+ }
+
+ // get the capitalized characters in the actual checksum.
+ string memory actual = _toChecksumString(accountAddress);
+
+ // compare provided string to actual checksum string to test for validity.
+ //TODO: check with ICONDAO team, this fails due to the capitalization of the actual address
+ /* require(
+ keccak256(abi.encodePacked(actual)) ==
+ keccak256(abi.encodePacked(account)),
+ "Invalid checksum"
+ ); */
+ }
+
+ /**
+ * @dev Get a checksummed string hex representation of an account address.
+ * @param account address The account to get the checksum for.
+ * @return The checksummed account string in ASCII format. Note that leading
+ * "0x" is not included.
+ */
+ function toString(address account) internal pure returns (string memory) {
+ // call internal function for converting an account to a checksummed string.
+ return _toChecksumString(account);
+ }
+
+ /**
+ * @dev Get a fixed-size array of whether or not each character in an account
+ * will be capitalized in the checksum.
+ * @param account address The account to get the checksum capitalization
+ * information for.
+ * @return A fixed-size array of booleans that signify if each character or
+ * "nibble" of the hex encoding of the address will be capitalized by the
+ * checksum.
+ */
+ function getChecksumCapitalizedCharacters(address account)
+ internal
+ pure
+ returns (bool[40] memory)
+ {
+ // call internal function for computing characters capitalized in checksum.
+ return _toChecksumCapsFlags(account);
+ }
+
+ /**
+ * @dev Determine whether a string hex representation of an account address
+ * matches the ERC-55 checksum of that address.
+ * @param accountChecksum string The checksummed account string in ASCII
+ * format. Note that a leading "0x" MUST NOT be included.
+ * @return A boolean signifying whether or not the checksum is valid.
+ */
+ function isChecksumValid(string calldata accountChecksum)
+ internal
+ pure
+ returns (bool)
+ {
+ // call internal function for validating checksum strings.
+ return _isChecksumValid(accountChecksum);
+ }
+
+ function _toChecksumString(address account)
+ internal
+ pure
+ returns (string memory asciiString)
+ {
+ // convert the account argument from address to bytes.
+ bytes20 data = bytes20(account);
+
+ // create an in-memory fixed-size bytes array.
+ bytes memory asciiBytes = new bytes(40);
+
+ // declare variable types.
+ uint8 b;
+ uint8 leftNibble;
+ uint8 rightNibble;
+ bool leftCaps;
+ bool rightCaps;
+ uint8 asciiOffset;
+
+ // get the capitalized characters in the actual checksum.
+ bool[40] memory caps = _toChecksumCapsFlags(account);
+
+ // iterate over bytes, processing left and right nibble in each iteration.
+ for (uint256 i = 0; i < data.length; i++) {
+ // locate the byte and extract each nibble.
+ b = uint8(uint160(data) / (2**(8 * (19 - i))));
+ leftNibble = b / 16;
+ rightNibble = b - 16 * leftNibble;
+
+ // locate and extract each capitalization status.
+ leftCaps = caps[2 * i];
+ rightCaps = caps[2 * i + 1];
+
+ // get the offset from nibble value to ascii character for left nibble.
+ asciiOffset = _getAsciiOffset(leftNibble, leftCaps);
+
+ // add the converted character to the byte array.
+ asciiBytes[2 * i] = byte(leftNibble + asciiOffset);
+
+ // get the offset from nibble value to ascii character for right nibble.
+ asciiOffset = _getAsciiOffset(rightNibble, rightCaps);
+
+ // add the converted character to the byte array.
+ asciiBytes[2 * i + 1] = byte(rightNibble + asciiOffset);
+ }
+
+ return string(abi.encodePacked("0x", string(asciiBytes)));
+ }
+
+ function _toChecksumCapsFlags(address account)
+ internal
+ pure
+ returns (bool[40] memory characterCapitalized)
+ {
+ // convert the address to bytes.
+ bytes20 a = bytes20(account);
+
+ // hash the address (used to calculate checksum).
+ bytes32 b = keccak256(abi.encodePacked(_toAsciiString(a)));
+
+ // declare variable types.
+ uint8 leftNibbleAddress;
+ uint8 rightNibbleAddress;
+ uint8 leftNibbleHash;
+ uint8 rightNibbleHash;
+
+ // iterate over bytes, processing left and right nibble in each iteration.
+ for (uint256 i; i < a.length; i++) {
+ // locate the byte and extract each nibble for the address and the hash.
+ rightNibbleAddress = uint8(a[i]) % 16;
+ leftNibbleAddress = (uint8(a[i]) - rightNibbleAddress) / 16;
+ rightNibbleHash = uint8(b[i]) % 16;
+ leftNibbleHash = (uint8(b[i]) - rightNibbleHash) / 16;
+
+ characterCapitalized[2 * i] = (leftNibbleAddress > 9 &&
+ leftNibbleHash > 7);
+ characterCapitalized[2 * i + 1] = (rightNibbleAddress > 9 &&
+ rightNibbleHash > 7);
+ }
+ }
+
+ function _isChecksumValid(string memory provided)
+ internal
+ pure
+ returns (bool ok)
+ {
+ // convert the provided string into account type.
+ address account = _toAddress(provided);
+
+ // return false in the event the account conversion returned null address.
+ if (account == address(0)) {
+ // ensure that provided address is not also the null address first.
+ bytes memory b = bytes(provided);
+ for (uint256 i; i < b.length; i++) {
+ if (b[i] != hex"30") {
+ return false;
+ }
+ }
+ }
+
+ // get the capitalized characters in the actual checksum.
+ string memory actual = _toChecksumString(account);
+
+ // compare provided string to actual checksum string to test for validity.
+ return (keccak256(abi.encodePacked(actual)) ==
+ keccak256(abi.encodePacked(provided)));
+ }
+
+ function _getAsciiOffset(uint8 nibble, bool caps)
+ internal
+ pure
+ returns (uint8 offset)
+ {
+ // to convert to ascii characters, add 48 to 0-9, 55 to A-F, & 87 to a-f.
+ if (nibble < 10) {
+ offset = 48;
+ } else if (caps) {
+ offset = 55;
+ } else {
+ offset = 87;
+ }
+ }
+
+ function _toAddress(string memory account)
+ internal
+ pure
+ returns (address accountAddress)
+ {
+ // convert the account argument from address to bytes.
+ bytes memory accountBytes = bytes(account);
+
+ // create a new fixed-size byte array for the ascii bytes of the address.
+ bytes memory accountAddressBytes = new bytes(20);
+
+ // declare variable types.
+ uint8 b;
+ uint8 nibble;
+ uint8 asciiOffset;
+
+ // only proceed if the provided string has a length of 40.
+ if (accountBytes.length == 40) {
+ for (uint256 i; i < 40; i++) {
+ // get the byte in question.
+ b = uint8(accountBytes[i]);
+
+ // ensure that the byte is a valid ascii character (0-9, A-F, a-f)
+ if (b < 48) return address(0);
+ if (57 < b && b < 65) return address(0);
+ if (70 < b && b < 97) return address(0);
+ if (102 < b) return address(0); //bytes(hex"");
+
+ // find the offset from ascii encoding to the nibble representation.
+ if (b < 65) {
+ // 0-9
+ asciiOffset = 48;
+ } else if (70 < b) {
+ // a-f
+ asciiOffset = 87;
+ } else {
+ // A-F
+ asciiOffset = 55;
+ }
+
+ // store left nibble on even iterations, then store byte on odd ones.
+ if (i % 2 == 0) {
+ nibble = b - asciiOffset;
+ } else {
+ accountAddressBytes[(i - 1) / 2] = (
+ byte(16 * nibble + (b - asciiOffset))
+ );
+ }
+ }
+
+ // pack up the fixed-size byte array and cast it to accountAddress.
+ bytes memory packed = abi.encodePacked(accountAddressBytes);
+ assembly {
+ accountAddress := mload(add(packed, 20))
+ }
+ }
+ }
+
+ // based on https://ethereum.stackexchange.com/a/56499/48410
+ function _toAsciiString(bytes20 data)
+ internal
+ pure
+ returns (string memory asciiString)
+ {
+ // create an in-memory fixed-size bytes array.
+ bytes memory asciiBytes = new bytes(40);
+
+ // declare variable types.
+ uint8 b;
+ uint8 leftNibble;
+ uint8 rightNibble;
+
+ // iterate over bytes, processing left and right nibble in each iteration.
+ for (uint256 i = 0; i < data.length; i++) {
+ // locate the byte and extract each nibble.
+ b = uint8(uint160(data) / (2**(8 * (19 - i))));
+ leftNibble = b / 16;
+ rightNibble = b - 16 * leftNibble;
+
+ // to convert to ascii characters, add 48 to 0-9 and 87 to a-f.
+ asciiBytes[2 * i] = byte(leftNibble + (leftNibble < 10 ? 48 : 87));
+ asciiBytes[2 * i + 1] = byte(
+ rightNibble + (rightNibble < 10 ? 48 : 87)
+ );
+ }
+
+ return string(asciiBytes);
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/RLPDecode.sol b/solidity/nativecoinERC20/contracts/libraries/RLPDecode.sol
new file mode 100644
index 00000000..a7764aa9
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/RLPDecode.sol
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/*
+ * Change supporting solidity compiler version
+ * The original code can be found via this link: https://github.com/hamdiallam/Solidity-RLP.git
+ */
+
+library RLPDecode {
+ uint8 private constant STRING_SHORT_START = 0x80;
+ uint8 private constant STRING_LONG_START = 0xb8;
+ uint8 private constant LIST_SHORT_START = 0xc0;
+ uint8 private constant LIST_LONG_START = 0xf8;
+ uint8 private constant WORD_SIZE = 32;
+
+ struct RLPItem {
+ uint256 len;
+ uint256 memPtr;
+ }
+
+ struct Iterator {
+ RLPItem item; // Item that's being iterated over.
+ uint256 nextPtr; // Position of the next item in the list.
+ }
+
+ /*
+ * @dev Returns the next element in the iteration. Reverts if it has not next element.
+ * @param self The iterator.
+ * @return The next element in the iteration.
+ */
+ function next(Iterator memory self) internal pure returns (RLPItem memory) {
+ require(hasNext(self), "Must have next elements");
+
+ uint256 ptr = self.nextPtr;
+ uint256 itemLength = _itemLength(ptr);
+ self.nextPtr = ptr + itemLength;
+
+ return RLPItem(itemLength, ptr);
+ }
+
+ /*
+ * @dev Returns true if the iteration has more elements.
+ * @param self The iterator.
+ * @return true if the iteration has more elements.
+ */
+ function hasNext(Iterator memory self) internal pure returns (bool) {
+ RLPItem memory item = self.item;
+ return self.nextPtr < item.memPtr + item.len;
+ }
+
+ /*
+ * @param item RLP encoded bytes
+ */
+ function toRlpItem(bytes memory item)
+ internal
+ pure
+ returns (RLPItem memory)
+ {
+ uint256 memPtr;
+ assembly {
+ memPtr := add(item, 0x20)
+ }
+
+ return RLPItem(item.length, memPtr);
+ }
+
+ /*
+ * @dev Create an iterator. Reverts if item is not a list.
+ * @param self The RLP item.
+ * @return An 'Iterator' over the item.
+ */
+ function iterator(RLPItem memory self)
+ internal
+ pure
+ returns (Iterator memory)
+ {
+ require(isList(self), "Must be a list");
+
+ uint256 ptr = self.memPtr + _payloadOffset(self.memPtr);
+ return Iterator(self, ptr);
+ }
+
+ /*
+ * @param item RLP encoded bytes
+ */
+ function rlpLen(RLPItem memory item) internal pure returns (uint256) {
+ return item.len;
+ }
+
+ /*
+ * @param item RLP encoded bytes
+ */
+ function payloadLen(RLPItem memory item) internal pure returns (uint256) {
+ return item.len - _payloadOffset(item.memPtr);
+ }
+
+ /*
+ * @param item RLP encoded list in bytes
+ */
+ function toList(RLPItem memory item)
+ internal
+ pure
+ returns (RLPItem[] memory)
+ {
+ require(isList(item), "Must be a list");
+
+ uint256 items = numItems(item);
+ RLPItem[] memory result = new RLPItem[](items);
+
+ uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr);
+ uint256 dataLen;
+ for (uint256 i = 0; i < items; i++) {
+ dataLen = _itemLength(memPtr);
+ result[i] = RLPItem(dataLen, memPtr);
+ memPtr = memPtr + dataLen;
+ }
+
+ return result;
+ }
+
+ // @return indicator whether encoded payload is a list. negate this function call for isData.
+ function isList(RLPItem memory item) internal pure returns (bool) {
+ if (item.len == 0) return false;
+
+ uint8 byte0;
+ uint256 memPtr = item.memPtr;
+ assembly {
+ byte0 := byte(0, mload(memPtr))
+ }
+
+ if (byte0 < LIST_SHORT_START) return false;
+ return true;
+ }
+
+ /** RLPItem conversions into data types **/
+
+ // @returns raw rlp encoding in bytes
+ function toRlpBytes(RLPItem memory item)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory result = new bytes(item.len);
+ if (result.length == 0) return result;
+
+ uint256 ptr;
+ assembly {
+ ptr := add(0x20, result)
+ }
+
+ copy(item.memPtr, ptr, item.len);
+ return result;
+ }
+
+ // any non-zero byte except "0x80" is considered true
+ function toBoolean(RLPItem memory item) internal pure returns (bool) {
+ require(item.len == 1, "Must have length 1");
+ uint256 result;
+ uint256 memPtr = item.memPtr;
+ assembly {
+ result := byte(0, mload(memPtr))
+ }
+
+ // SEE Github Issue #5.
+ // Summary: Most commonly used RLP libraries (i.e Geth) will encode
+ // "0" as "0x80" instead of as "0". We handle this edge case explicitly
+ // here.
+ if (result == 0 || result == STRING_SHORT_START) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function toAddress(RLPItem memory item) internal pure returns (address) {
+ // 1 byte for the length prefix
+ require(item.len == 21, "Must have length 21");
+
+ return address(uint160(toUint(item)));
+ }
+
+ function toUint(RLPItem memory item) internal pure returns (uint256) {
+ require(item.len > 0 && item.len <= 33, "Invalid uint number");
+
+ uint256 offset = _payloadOffset(item.memPtr);
+ uint256 len = item.len - offset;
+
+ uint256 result;
+ uint256 memPtr = item.memPtr + offset;
+ assembly {
+ result := mload(memPtr)
+
+ // shfit to the correct location if neccesary
+ if lt(len, 32) {
+ result := div(result, exp(256, sub(32, len)))
+ }
+ }
+
+ return result;
+ }
+
+ function toInt(RLPItem memory item) internal pure returns (int256) {
+ if ((toBytes(item)[0] & 0x80) == 0x80) {
+ return int256(toUint(item) - 2**(toBytes(item).length * 8));
+ }
+
+ return int256(toUint(item));
+ }
+
+ // enforces 32 byte length
+ function toUintStrict(RLPItem memory item) internal pure returns (uint256) {
+ // one byte prefix
+ require(item.len == 33, "Must have length 33");
+
+ uint256 result;
+ uint256 memPtr = item.memPtr + 1;
+ assembly {
+ result := mload(memPtr)
+ }
+
+ return result;
+ }
+
+ function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
+ require(item.len > 0, "Invalid length");
+
+ uint256 offset = _payloadOffset(item.memPtr);
+ uint256 len = item.len - offset; // data length
+ bytes memory result = new bytes(len);
+
+ uint256 destPtr;
+ assembly {
+ destPtr := add(0x20, result)
+ }
+
+ copy(item.memPtr + offset, destPtr, len);
+ return result;
+ }
+
+ /*
+ * Private Helpers
+ */
+
+ // @return number of payload items inside an encoded list.
+ function numItems(RLPItem memory item) private pure returns (uint256) {
+ if (item.len == 0) return 0;
+
+ uint256 count = 0;
+ uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr);
+ uint256 endPtr = item.memPtr + item.len;
+ while (currPtr < endPtr) {
+ currPtr = currPtr + _itemLength(currPtr); // skip over an item
+ count++;
+ }
+
+ return count;
+ }
+
+ // @return entire rlp item byte length
+ function _itemLength(uint256 memPtr) private pure returns (uint256) {
+ uint256 itemLen;
+ uint256 byte0;
+ assembly {
+ byte0 := byte(0, mload(memPtr))
+ }
+
+ if (byte0 < STRING_SHORT_START) itemLen = 1;
+ else if (byte0 < STRING_LONG_START)
+ itemLen = byte0 - STRING_SHORT_START + 1;
+ else if (byte0 < LIST_SHORT_START) {
+ assembly {
+ let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
+ memPtr := add(memPtr, 1) // skip over the first byte
+
+ /* 32 byte word size */
+ let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ } else if (byte0 < LIST_LONG_START) {
+ itemLen = byte0 - LIST_SHORT_START + 1;
+ } else {
+ assembly {
+ let byteLen := sub(byte0, 0xf7)
+ memPtr := add(memPtr, 1)
+
+ let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ }
+
+ return itemLen;
+ }
+
+ // @return number of bytes until the data
+ function _payloadOffset(uint256 memPtr) private pure returns (uint256) {
+ uint256 byte0;
+ assembly {
+ byte0 := byte(0, mload(memPtr))
+ }
+
+ if (byte0 < STRING_SHORT_START) return 0;
+ else if (
+ byte0 < STRING_LONG_START ||
+ (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)
+ ) return 1;
+ else if (byte0 < LIST_SHORT_START)
+ // being explicit
+ return byte0 - (STRING_LONG_START - 1) + 1;
+ else return byte0 - (LIST_LONG_START - 1) + 1;
+ }
+
+ /*
+ * @param src Pointer to source
+ * @param dest Pointer to destination
+ * @param len Amount of memory to copy from the source
+ */
+ function copy(
+ uint256 src,
+ uint256 dest,
+ uint256 len
+ ) private pure {
+ if (len == 0) return;
+
+ // copy as many word sizes as possible
+ for (; len >= WORD_SIZE; len -= WORD_SIZE) {
+ assembly {
+ mstore(dest, mload(src))
+ }
+
+ src += WORD_SIZE;
+ dest += WORD_SIZE;
+ }
+
+ // left over bytes. Mask is used to remove unwanted bytes from the word
+ uint256 mask = 256**(WORD_SIZE - len) - 1;
+ assembly {
+ let srcpart := and(mload(src), not(mask)) // zero out src
+ let destpart := and(mload(dest), mask) // retrieve the bytes
+ mstore(dest, or(destpart, srcpart))
+ }
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/RLPDecodeStruct.sol b/solidity/nativecoinERC20/contracts/libraries/RLPDecodeStruct.sol
new file mode 100644
index 00000000..4ddf2029
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/RLPDecodeStruct.sol
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./RLPDecode.sol";
+import "./Types.sol";
+
+//import "./RLPEncode.sol";
+
+library RLPDecodeStruct {
+ using RLPDecode for RLPDecode.RLPItem;
+ using RLPDecode for RLPDecode.Iterator;
+ using RLPDecode for bytes;
+
+ using RLPDecodeStruct for bytes;
+
+ uint8 private constant LIST_SHORT_START = 0xc0;
+ uint8 private constant LIST_LONG_START = 0xf7;
+
+ function decodeBMCService(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BMCService memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ return
+ Types.BMCService(
+ string(ls[0].toBytes()),
+ ls[1].toBytes() // bytes array of RLPEncode(Data)
+ );
+ }
+
+ function decodeGatherFeeMessage(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.GatherFeeMessage memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ RLPDecode.RLPItem[] memory subList = ls[1].toList();
+ string[] memory _svcs = new string[](subList.length);
+ for (uint256 i = 0; i < subList.length; i++) {
+ _svcs[i] = string(subList[i].toBytes());
+ }
+ return Types.GatherFeeMessage(string(ls[0].toBytes()), _svcs);
+ }
+
+ function decodeEventMessage(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.EventMessage memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ return
+ Types.EventMessage(
+ string(ls[0].toBytes()),
+ Types.Connection(
+ string(ls[1].toList()[0].toBytes()),
+ string(ls[1].toList()[1].toBytes())
+ )
+ );
+ }
+
+ function decodeCoinRegister(bytes memory _rlp)
+ internal
+ pure
+ returns (string[] memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ string[] memory _coins = new string[](ls.length);
+ for (uint256 i = 0; i < ls.length; i++) {
+ _coins[i] = string(ls[i].toBytes());
+ }
+ return _coins;
+ }
+
+ function decodeBMCMessage(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BMCMessage memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ return
+ Types.BMCMessage(
+ string(ls[0].toBytes()),
+ string(ls[1].toBytes()),
+ string(ls[2].toBytes()),
+ ls[3].toInt(),
+ ls[4].toBytes() // bytes array of RLPEncode(ServiceMessage)
+ );
+ }
+
+ function decodeServiceMessage(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.ServiceMessage memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ return
+ Types.ServiceMessage(
+ Types.ServiceType(ls[0].toUint()),
+ ls[1].toBytes() // bytes array of RLPEncode(Data)
+ );
+ }
+
+ function decodeTransferCoinMsg(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.TransferCoin memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ Types.Asset[] memory assets = new Types.Asset[](ls[2].toList().length);
+ RLPDecode.RLPItem[] memory rlpAssets = ls[2].toList();
+ for (uint256 i = 0; i < ls[2].toList().length; i++) {
+ assets[i] = Types.Asset(
+ string(rlpAssets[i].toList()[0].toBytes()),
+ rlpAssets[i].toList()[1].toUint()
+ );
+ }
+ return
+ Types.TransferCoin(
+ string(ls[0].toBytes()),
+ string(ls[1].toBytes()),
+ assets
+ );
+ }
+
+ function decodeResponse(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.Response memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ return Types.Response(ls[0].toUint(), string(ls[1].toBytes()));
+ }
+
+ function decodeBlockHeader(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BlockHeader memory)
+ {
+ // Decode RLP bytes into a list of items
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ bool isSPREmpty = true;
+ if (ls[10].toBytes().length == 0) {
+ return
+ Types.BlockHeader(
+ ls[0].toUint(),
+ ls[1].toUint(),
+ ls[2].toUint(),
+ ls[3].toBytes(),
+ ls[4].toBytes(),
+ ls[5].toBytes(),
+ ls[6].toBytes(),
+ ls[7].toBytes(),
+ ls[8].toBytes(),
+ ls[9].toBytes(),
+ Types.SPR("", "", ""),
+ isSPREmpty
+ );
+ }
+ RLPDecode.RLPItem[] memory subList =
+ ls[10].toBytes().toRlpItem().toList();
+ isSPREmpty = false;
+ return
+ Types.BlockHeader(
+ ls[0].toUint(),
+ ls[1].toUint(),
+ ls[2].toUint(),
+ ls[3].toBytes(),
+ ls[4].toBytes(),
+ ls[5].toBytes(),
+ ls[6].toBytes(),
+ ls[7].toBytes(),
+ ls[8].toBytes(),
+ ls[9].toBytes(),
+ Types.SPR(
+ subList[0].toBytes(),
+ subList[1].toBytes(),
+ subList[2].toBytes()
+ ),
+ isSPREmpty
+ );
+ }
+
+ // Votes item consists of:
+ // round as integer
+ // blockPartSetID is a list that consists of two items - integer and bytes
+ // and TS[] ts_list (an array of list)
+ function decodeVotes(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.Votes memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+
+ Types.TS[] memory tsList = new Types.TS[](ls[2].toList().length);
+ RLPDecode.RLPItem[] memory rlpTs = ls[2].toList();
+ for (uint256 i = 0; i < ls[2].toList().length; i++) {
+ tsList[i] = Types.TS(
+ rlpTs[i].toList()[0].toUint(),
+ rlpTs[i].toList()[1].toBytes()
+ );
+ }
+ return
+ Types.Votes(
+ ls[0].toUint(),
+ Types.BPSI(
+ ls[1].toList()[0].toUint(),
+ ls[1].toList()[1].toBytes()
+ ),
+ tsList
+ );
+ }
+
+ // Wait for confirmation
+ function decodeBlockWitness(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BlockWitness memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+
+ bytes[] memory witnesses = new bytes[](ls[1].toList().length);
+ // witnesses is an array of hash of leaf node
+ // The array size may also vary, thus loop is needed therein
+ for (uint256 i = 0; i < ls[1].toList().length; i++) {
+ witnesses[i] = ls[1].toList()[i].toBytes();
+ }
+ return Types.BlockWitness(ls[0].toUint(), witnesses);
+ }
+
+ function decodeEventProof(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.EventProof memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ RLPDecode.RLPItem[] memory data = ls[1].toBytes().toRlpItem().toList();
+
+ bytes[] memory eventMptNode = new bytes[](data.length);
+ for (uint256 i = 0; i < data.length; i++) {
+ eventMptNode[i] = data[i].toBytes();
+ }
+ return Types.EventProof(ls[0].toUint(), eventMptNode);
+ }
+
+ function decodeBlockUpdate(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BlockUpdate memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+
+ // Types.BlockHeader memory _bh;
+ Types.BlockHeader memory _bh = ls[0].toBytes().decodeBlockHeader();
+ Types.Votes memory _v = ls[1].toBytes().decodeVotes();
+ // Types.Votes memory _v;
+
+ // BlockUpdate may or may not include the RLP of addresses of validators
+ // In that case, RLP_ENCODE([bytes]) == EMPTY_LIST_HEAD_START == 0xF800
+ // Thus, length of data will be 0. Therein, loop will be skipped
+ // and the _validators[] will be empty
+ // Otherwise, executing normally to read and assign value into the array _validators[]
+ bytes[] memory _validators;
+ if (ls[2].toBytes().length != 0) {
+ _validators = new bytes[](ls[2].toList().length);
+ for (uint256 i = 0; i < ls[2].toList().length; i++) {
+ _validators[i] = ls[2].toList()[i].toBytes();
+ }
+ }
+ return Types.BlockUpdate(_bh, _v, _validators);
+ }
+
+ function decodeReceiptProof(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.ReceiptProof memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ RLPDecode.RLPItem[] memory receiptList =
+ ls[1].toBytes().toRlpItem().toList();
+
+ bytes[] memory txReceipts = new bytes[](receiptList.length);
+ for (uint256 i = 0; i < receiptList.length; i++) {
+ txReceipts[i] = receiptList[i].toBytes();
+ }
+
+ Types.EventProof[] memory _ep =
+ new Types.EventProof[](ls[2].toList().length);
+ for (uint256 i = 0; i < ls[2].toList().length; i++) {
+ _ep[i] = Types.EventProof(
+ ls[2].toList()[i].toList()[0].toUint(),
+ ls[2].toList()[i].toList()[1].toBytes().decodeEventLog()
+ );
+ }
+
+ return Types.ReceiptProof(ls[0].toUint(), txReceipts, _ep);
+ }
+
+ function decodeEventLog(bytes memory _rlp)
+ internal
+ pure
+ returns (bytes[] memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ bytes[] memory eventMptNode = new bytes[](ls.length);
+ for (uint256 i = 0; i < ls.length; i++) {
+ eventMptNode[i] = ls[i].toBytes();
+ }
+ return eventMptNode;
+ }
+
+ function decodeBlockProof(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.BlockProof memory)
+ {
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+
+ Types.BlockHeader memory _bh = ls[0].toBytes().decodeBlockHeader();
+ Types.BlockWitness memory _bw = ls[1].toBytes().decodeBlockWitness();
+
+ return Types.BlockProof(_bh, _bw);
+ }
+
+ function decodeRelayMessage(bytes memory _rlp)
+ internal
+ pure
+ returns (Types.RelayMessage memory)
+ {
+ // _rlp.toRlpItem() removes the LIST_HEAD_START of RelayMessage
+ // then .toList() to itemize all fields in the RelayMessage
+ // which are [RLP_ENCODE(BlockUpdate)], RLP_ENCODE(BlockProof), and
+ // the RLP_ENCODE(ReceiptProof)
+ RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList();
+ // return (
+ // ls[0].toList()[0].toBytes().toRlpItem().toList()[1].toBytes().toRlpItem().toList()[2].toList()[0].toList()[1].toBytes()
+ // );
+
+ // If [RLP_ENCODE(BlockUpdate)] was empty, it should be started by 0xF800
+ // therein, ls[0].toBytes() will be null (length = 0)
+ // Otherwise, create an array of BlockUpdate struct to decode
+ Types.BlockUpdate[] memory _buArray;
+ if (ls[0].toBytes().length != 0) {
+ _buArray = new Types.BlockUpdate[](ls[0].toList().length);
+ for (uint256 i = 0; i < ls[0].toList().length; i++) {
+ // Each of items inside an array [RLP_ENCODE(BlockUpdate)]
+ // is a string which defines RLP_ENCODE(BlockUpdate)
+ // that contains a LIST_HEAD_START and multiple RLP of data
+ // ls[0].toList()[i].toBytes() returns bytes presentation of
+ // RLP_ENCODE(BlockUpdate)
+ _buArray[i] = ls[0].toList()[i].toBytes().decodeBlockUpdate();
+ }
+ }
+ bool isBPEmpty = true;
+ Types.BlockProof memory _bp;
+ // If RLP_ENCODE(BlockProof) is omitted,
+ // ls[1].toBytes() should be null (length = 0)
+ if (ls[1].toBytes().length != 0) {
+ _bp = ls[1].toBytes().decodeBlockProof();
+ isBPEmpty = false; // add this field into RelayMessage
+ // to specify whether BlockProof is omitted
+ // to make it easy on encoding
+ // it will not be serialized thereafter
+ }
+
+ bool isRPEmpty = true;
+ Types.ReceiptProof[] memory _rp;
+ // If [RLP_ENCODE(ReceiptProof)] is omitted,
+ // ls[2].toBytes() should be null (length = 0)
+ if (ls[2].toBytes().length != 0) {
+ _rp = new Types.ReceiptProof[](ls[2].toList().length);
+ for (uint256 i = 0; i < ls[2].toList().length; i++) {
+ _rp[i] = ls[2].toList()[i].toBytes().decodeReceiptProof();
+ }
+ isRPEmpty = false; // add this field into RelayMessage
+ // to specify whether ReceiptProof is omitted
+ // to make it easy on encoding
+ // it will not be serialized thereafter
+ }
+ return Types.RelayMessage(_buArray, _bp, isBPEmpty, _rp, isRPEmpty);
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/RLPEncode.sol b/solidity/nativecoinERC20/contracts/libraries/RLPEncode.sol
new file mode 100644
index 00000000..a7bb08db
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/RLPEncode.sol
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/**
+ * @title RLPEncode
+ * @dev A simple RLP encoding library.
+ * @author Bakaoh
+ * The original code was modified. For more info, please check the link:
+ * https://github.com/bakaoh/solidity-rlp-encode.git
+ */
+library RLPEncode {
+ int8 internal constant MAX_INT8 = type(int8).max;
+ int16 internal constant MAX_INT16 = type(int16).max;
+ int24 internal constant MAX_INT24 = type(int24).max;
+ int32 internal constant MAX_INT32 = type(int32).max;
+ int40 internal constant MAX_INT40 = type(int40).max;
+ int48 internal constant MAX_INT48 = type(int48).max;
+ int56 internal constant MAX_INT56 = type(int56).max;
+ int64 internal constant MAX_INT64 = type(int64).max;
+ int72 internal constant MAX_INT72 = type(int72).max;
+ int80 internal constant MAX_INT80 = type(int80).max;
+ int88 internal constant MAX_INT88 = type(int88).max;
+ int96 internal constant MAX_INT96 = type(int96).max;
+ int104 internal constant MAX_INT104 = type(int104).max;
+ int112 internal constant MAX_INT112 = type(int112).max;
+ int120 internal constant MAX_INT120 = type(int120).max;
+ int128 internal constant MAX_INT128 = type(int128).max;
+
+ uint8 internal constant MAX_UINT8 = type(uint8).max;
+ uint16 internal constant MAX_UINT16 = type(uint16).max;
+ uint24 internal constant MAX_UINT24 = type(uint24).max;
+ uint32 internal constant MAX_UINT32 = type(uint32).max;
+ uint40 internal constant MAX_UINT40 = type(uint40).max;
+ uint48 internal constant MAX_UINT48 = type(uint48).max;
+ uint56 internal constant MAX_UINT56 = type(uint56).max;
+ uint64 internal constant MAX_UINT64 = type(uint64).max;
+ uint72 internal constant MAX_UINT72 = type(uint72).max;
+ uint80 internal constant MAX_UINT80 = type(uint80).max;
+ uint88 internal constant MAX_UINT88 = type(uint88).max;
+ uint96 internal constant MAX_UINT96 = type(uint96).max;
+ uint104 internal constant MAX_UINT104 = type(uint104).max;
+ uint112 internal constant MAX_UINT112 = type(uint112).max;
+ uint120 internal constant MAX_UINT120 = type(uint120).max;
+ uint128 internal constant MAX_UINT128 = type(uint128).max;
+
+ /*
+ * Internal functions
+ */
+
+ /**
+ * @dev RLP encodes a byte string.
+ * @param self The byte string to encode.
+ * @return The RLP encoded string in bytes.
+ */
+ function encodeBytes(bytes memory self)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory encoded;
+ if (self.length == 1 && uint8(self[0]) <= 128) {
+ encoded = self;
+ } else {
+ encoded = concat(encodeLength(self.length, 128), self);
+ }
+ return encoded;
+ }
+
+ /**
+ * @dev RLP encodes a list of RLP encoded byte byte strings.
+ * @param self The list of RLP encoded byte strings.
+ * @return The RLP encoded list of items in bytes.
+ */
+ function encodeList(bytes[] memory self)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory list = flatten(self);
+ return concat(encodeLength(list.length, 192), list);
+ }
+
+ /**
+ * @dev RLP encodes a string.
+ * @param self The string to encode.
+ * @return The RLP encoded string in bytes.
+ */
+ function encodeString(string memory self)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ return encodeBytes(bytes(self));
+ }
+
+ /**
+ * @dev RLP encodes an address.
+ * @param self The address to encode.
+ * @return The RLP encoded address in bytes.
+ */
+ function encodeAddress(address self) internal pure returns (bytes memory) {
+ bytes memory inputBytes;
+ assembly {
+ let m := mload(0x40)
+ mstore(
+ add(m, 20),
+ xor(0x140000000000000000000000000000000000000000, self)
+ )
+ mstore(0x40, add(m, 52))
+ inputBytes := m
+ }
+ return encodeBytes(inputBytes);
+ }
+
+ /**
+ * @dev RLP encodes a uint.
+ * @param self The uint to encode.
+ * @return The RLP encoded uint in bytes.
+ */
+ function encodeUint(uint256 self) internal pure returns (bytes memory) {
+ uint256 nBytes = bitLength(self) / 8 + 1;
+ bytes memory uintBytes = encodeUintByLength(self);
+ if (nBytes - uintBytes.length > 0) {
+ uintBytes = abi.encodePacked(bytes1(0), uintBytes);
+ }
+ return encodeBytes(uintBytes);
+ }
+
+ /**
+ * @dev convert int to strict bytes.
+ * @notice only handle to int128 due to contract code size limit
+ * @param n The int to convert.
+ * @return The int in strict bytes without padding.
+ */
+ function intToStrictBytes(int256 n) internal pure returns (bytes memory) {
+ if (-MAX_INT8 - 1 <= n && n <= MAX_INT8) {
+ return abi.encodePacked(int8(n));
+ } else if (-MAX_INT16 - 1 <= n && n <= MAX_INT16) {
+ return abi.encodePacked(int16(n));
+ } else if (-MAX_INT24 - 1 <= n && n <= MAX_INT24) {
+ return abi.encodePacked(int24(n));
+ } else if (-MAX_INT32 - 1 <= n && n <= MAX_INT32) {
+ return abi.encodePacked(int32(n));
+ } else if (-MAX_INT40 - 1 <= n && n <= MAX_INT40) {
+ return abi.encodePacked(int40(n));
+ } else if (-MAX_INT48 - 1 <= n && n <= MAX_INT48) {
+ return abi.encodePacked(int48(n));
+ } else if (-MAX_INT56 - 1 <= n && n <= MAX_INT56) {
+ return abi.encodePacked(int56(n));
+ } else if (-MAX_INT64 - 1 <= n && n <= MAX_INT64) {
+ return abi.encodePacked(int64(n));
+ } else if (-MAX_INT72 - 1 <= n && n <= MAX_INT72) {
+ return abi.encodePacked(int72(n));
+ } else if (-MAX_INT80 - 1 <= n && n <= MAX_INT80) {
+ return abi.encodePacked(int80(n));
+ } else if (-MAX_INT88 - 1 <= n && n <= MAX_INT88) {
+ return abi.encodePacked(int88(n));
+ } else if (-MAX_INT96 - 1 <= n && n <= MAX_INT96) {
+ return abi.encodePacked(int96(n));
+ } else if (-MAX_INT104 - 1 <= n && n <= MAX_INT104) {
+ return abi.encodePacked(int104(n));
+ } else if (-MAX_INT112 - 1 <= n && n <= MAX_INT112) {
+ return abi.encodePacked(int112(n));
+ } else if (-MAX_INT120 - 1 <= n && n <= MAX_INT120) {
+ return abi.encodePacked(int120(n));
+ }
+ require(
+ -MAX_INT128 - 1 <= n && n <= MAX_INT128,
+ "outOfBounds: [-2^128-1, 2^128]"
+ );
+ return abi.encodePacked(int128(n));
+ }
+
+ /**
+ * @dev RLP encodes an int.
+ * @param self The int to encode.
+ * @return The RLP encoded int in bytes.
+ */
+ function encodeInt(int256 self) internal pure returns (bytes memory) {
+ return encodeBytes(intToStrictBytes(self));
+ }
+
+ /**
+ * @dev RLP encodes a bool.
+ * @param self The bool to encode.
+ * @return The RLP encoded bool in bytes.
+ */
+ function encodeBool(bool self) internal pure returns (bytes memory) {
+ bytes memory encoded = new bytes(1);
+ encoded[0] = (self ? bytes1(0x01) : bytes1(0x00));
+ return encoded;
+ }
+
+ /*
+ * Private functions
+ */
+
+ /**
+ * @dev Encode the first byte, followed by the `len` in binary form if `length` is more than 55.
+ * @param len The length of the string or the payload.
+ * @param offset 128 if item is string, 192 if item is list.
+ * @return RLP encoded bytes.
+ */
+ function encodeLength(uint256 len, uint256 offset)
+ private
+ pure
+ returns (bytes memory)
+ {
+ bytes memory encoded;
+ if (len < 56) {
+ encoded = new bytes(1);
+ encoded[0] = bytes32(len + offset)[31];
+ } else {
+ uint256 lenLen;
+ uint256 i = 1;
+ while (len / i != 0) {
+ lenLen++;
+ i *= 256;
+ }
+
+ encoded = new bytes(lenLen + 1);
+ encoded[0] = bytes32(lenLen + offset + 55)[31];
+ for (i = 1; i <= lenLen; i++) {
+ encoded[i] = bytes32((len / (256**(lenLen - i))) % 256)[31];
+ }
+ }
+ return encoded;
+ }
+
+ /**
+ * @dev Encode integer in big endian binary form with no leading zeroes.
+ * @notice TODO: This should be optimized with assembly to save gas costs.
+ * @param _x The integer to encode.
+ * @return RLP encoded bytes.
+ */
+ function toBinary(uint256 _x) private pure returns (bytes memory) {
+ // Modify library to make it work properly when _x = 0
+ if (_x == 0) {
+ return abi.encodePacked(uint8(_x));
+ }
+ bytes memory b = new bytes(32);
+ assembly {
+ mstore(add(b, 32), _x)
+ }
+ uint256 i;
+ for (i = 0; i < 32; i++) {
+ if (b[i] != 0) {
+ break;
+ }
+ }
+ bytes memory res = new bytes(32 - i);
+ for (uint256 j = 0; j < res.length; j++) {
+ res[j] = b[i++];
+ }
+ return res;
+ }
+
+ /**
+ * @dev Copies a piece of memory to another location.
+ * @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol.
+ * @param _dest Destination location.
+ * @param _src Source location.
+ * @param _len Length of memory to copy.
+ */
+ function memcpy(
+ uint256 _dest,
+ uint256 _src,
+ uint256 _len
+ ) private pure {
+ uint256 dest = _dest;
+ uint256 src = _src;
+ uint256 len = _len;
+
+ for (; len >= 32; len -= 32) {
+ assembly {
+ mstore(dest, mload(src))
+ }
+ dest += 32;
+ src += 32;
+ }
+
+ uint256 mask = 256**(32 - len) - 1;
+ assembly {
+ let srcpart := and(mload(src), not(mask))
+ let destpart := and(mload(dest), mask)
+ mstore(dest, or(destpart, srcpart))
+ }
+ }
+
+ /**
+ * @dev Flattens a list of byte strings into one byte string.
+ * @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol.
+ * @param _list List of byte strings to flatten.
+ * @return The flattened byte string.
+ */
+ function flatten(bytes[] memory _list) private pure returns (bytes memory) {
+ if (_list.length == 0) {
+ return new bytes(0);
+ }
+
+ uint256 len;
+ uint256 i;
+ for (i = 0; i < _list.length; i++) {
+ len += _list[i].length;
+ }
+
+ bytes memory flattened = new bytes(len);
+ uint256 flattenedPtr;
+ assembly {
+ flattenedPtr := add(flattened, 0x20)
+ }
+
+ for (i = 0; i < _list.length; i++) {
+ bytes memory item = _list[i];
+
+ uint256 listPtr;
+ assembly {
+ listPtr := add(item, 0x20)
+ }
+
+ memcpy(flattenedPtr, listPtr, item.length);
+ flattenedPtr += _list[i].length;
+ }
+
+ return flattened;
+ }
+
+ /**
+ * @dev Concatenates two bytes.
+ * @notice From: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol.
+ * @param _preBytes First byte string.
+ * @param _postBytes Second byte string.
+ * @return Both byte string combined.
+ */
+ function concat(bytes memory _preBytes, bytes memory _postBytes)
+ private
+ pure
+ returns (bytes memory)
+ {
+ bytes memory tempBytes;
+
+ assembly {
+ tempBytes := mload(0x40)
+
+ let length := mload(_preBytes)
+ mstore(tempBytes, length)
+
+ let mc := add(tempBytes, 0x20)
+ let end := add(mc, length)
+
+ for {
+ let cc := add(_preBytes, 0x20)
+ } lt(mc, end) {
+ mc := add(mc, 0x20)
+ cc := add(cc, 0x20)
+ } {
+ mstore(mc, mload(cc))
+ }
+
+ length := mload(_postBytes)
+ mstore(tempBytes, add(length, mload(tempBytes)))
+
+ mc := end
+ end := add(mc, length)
+
+ for {
+ let cc := add(_postBytes, 0x20)
+ } lt(mc, end) {
+ mc := add(mc, 0x20)
+ cc := add(cc, 0x20)
+ } {
+ mstore(mc, mload(cc))
+ }
+
+ mstore(
+ 0x40,
+ and(
+ add(add(end, iszero(add(length, mload(_preBytes)))), 31),
+ not(31)
+ )
+ )
+ }
+
+ return tempBytes;
+ }
+
+ /**
+ * @dev convert uint to strict bytes.
+ * @notice only handle to uint128 due to contract code size limit
+ * @param length The uint to convert.
+ * @return The uint in strict bytes without padding.
+ */
+ function encodeUintByLength(uint256 length)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ if (length < MAX_UINT8) {
+ return abi.encodePacked(uint8(length));
+ } else if (length >= MAX_UINT8 && length < MAX_UINT16) {
+ return abi.encodePacked(uint16(length));
+ } else if (length >= MAX_UINT16 && length < MAX_UINT24) {
+ return abi.encodePacked(uint24(length));
+ } else if (length >= MAX_UINT24 && length < MAX_UINT32) {
+ return abi.encodePacked(uint32(length));
+ } else if (length >= MAX_UINT32 && length < MAX_UINT40) {
+ return abi.encodePacked(uint40(length));
+ } else if (length >= MAX_UINT40 && length < MAX_UINT48) {
+ return abi.encodePacked(uint48(length));
+ } else if (length >= MAX_UINT48 && length < MAX_UINT56) {
+ return abi.encodePacked(uint56(length));
+ } else if (length >= MAX_UINT56 && length < MAX_UINT64) {
+ return abi.encodePacked(uint64(length));
+ } else if (length >= MAX_UINT64 && length < MAX_UINT72) {
+ return abi.encodePacked(uint72(length));
+ } else if (length >= MAX_UINT72 && length < MAX_UINT80) {
+ return abi.encodePacked(uint80(length));
+ } else if (length >= MAX_UINT80 && length < MAX_UINT88) {
+ return abi.encodePacked(uint88(length));
+ } else if (length >= MAX_UINT88 && length < MAX_UINT96) {
+ return abi.encodePacked(uint96(length));
+ } else if (length >= MAX_UINT96 && length < MAX_UINT104) {
+ return abi.encodePacked(uint104(length));
+ } else if (length >= MAX_UINT104 && length < MAX_UINT112) {
+ return abi.encodePacked(uint112(length));
+ } else if (length >= MAX_UINT112 && length < MAX_UINT120) {
+ return abi.encodePacked(uint120(length));
+ }
+ require(
+ length >= MAX_UINT120 && length < MAX_UINT128,
+ "outOfBounds: [0, 2^128]"
+ );
+ return abi.encodePacked(uint128(length));
+ }
+
+ function bitLength(uint256 n) internal pure returns (uint256) {
+ uint256 count;
+ while (n != 0) {
+ count += 1;
+ n >>= 1;
+ }
+ return count;
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/RLPEncodeStruct.sol b/solidity/nativecoinERC20/contracts/libraries/RLPEncodeStruct.sol
new file mode 100644
index 00000000..d578afeb
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/RLPEncodeStruct.sol
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./RLPEncode.sol";
+import "./Types.sol";
+
+library RLPEncodeStruct {
+ using RLPEncode for bytes;
+ using RLPEncode for string;
+ using RLPEncode for uint256;
+ using RLPEncode for address;
+ using RLPEncode for int256;
+
+ using RLPEncodeStruct for Types.BlockHeader;
+ using RLPEncodeStruct for Types.BlockWitness;
+ using RLPEncodeStruct for Types.BlockUpdate;
+ using RLPEncodeStruct for Types.BlockProof;
+ using RLPEncodeStruct for Types.EventProof;
+ using RLPEncodeStruct for Types.ReceiptProof;
+ using RLPEncodeStruct for Types.Votes;
+ using RLPEncodeStruct for Types.RelayMessage;
+
+ uint8 private constant LIST_SHORT_START = 0xc0;
+ uint8 private constant LIST_LONG_START = 0xf7;
+
+ function encodeBMCService(Types.BMCService memory _bs)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ _bs.serviceType.encodeString(),
+ _bs.payload.encodeBytes()
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeGatherFeeMessage(Types.GatherFeeMessage memory _gfm)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+ for (uint256 i = 0; i < _gfm.svcs.length; i++) {
+ temp = abi.encodePacked(_gfm.svcs[i].encodeString());
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi.encodePacked(
+ _gfm.fa.encodeString(),
+ addLength(_rlp.length, false),
+ _rlp
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeEventMessage(Types.EventMessage memory _em)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ _em.conn.from.encodeString(),
+ _em.conn.to.encodeString()
+ );
+ _rlp = abi.encodePacked(
+ _em.eventType.encodeString(),
+ addLength(_rlp.length, false),
+ _rlp
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeCoinRegister(string[] memory _coins)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+ for (uint256 i = 0; i < _coins.length; i++) {
+ temp = abi.encodePacked(_coins[i].encodeString());
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeBMCMessage(Types.BMCMessage memory _bm)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ _bm.src.encodeString(),
+ _bm.dst.encodeString(),
+ _bm.svc.encodeString(),
+ _bm.sn.encodeInt(),
+ _bm.message.encodeBytes()
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeServiceMessage(Types.ServiceMessage memory _sm)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ uint256(_sm.serviceType).encodeUint(),
+ _sm.data.encodeBytes()
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeTransferCoinMsg(Types.TransferCoin memory _data)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+ for (uint256 i = 0; i < _data.assets.length; i++) {
+ temp = abi.encodePacked(
+ _data.assets[i].coinName.encodeString(),
+ _data.assets[i].value.encodeUint()
+ );
+ _rlp = abi.encodePacked(_rlp, addLength(temp.length, false), temp);
+ }
+ _rlp = abi.encodePacked(
+ _data.from.encodeString(),
+ _data.to.encodeString(),
+ addLength(_rlp.length, false),
+ _rlp
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeResponse(Types.Response memory _res)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ _res.code.encodeUint(),
+ _res.message.encodeString()
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeBlockHeader(Types.BlockHeader memory _bh)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ // Serialize the first 10 items in the BlockHeader
+ // patchTxHash and txHash might be empty.
+ // In that case, encoding these two items gives the result as 0xF800
+ // Similarly, logsBloom might be also empty
+ // But, encoding this item gives the result as 0x80
+ bytes memory _rlp =
+ abi.encodePacked(
+ _bh.version.encodeUint(),
+ _bh.height.encodeUint(),
+ _bh.timestamp.encodeUint(),
+ _bh.proposer.encodeBytes(),
+ _bh.prevHash.encodeBytes(),
+ _bh.voteHash.encodeBytes(),
+ _bh.nextValidators.encodeBytes()
+ );
+ bytes memory temp1;
+ if (_bh.patchTxHash.length != 0) {
+ temp1 = _bh.patchTxHash.encodeBytes();
+ } else {
+ temp1 = emptyListHeadStart();
+ }
+ _rlp = abi.encodePacked(_rlp, temp1);
+
+ if (_bh.txHash.length != 0) {
+ temp1 = _bh.txHash.encodeBytes();
+ } else {
+ temp1 = emptyListHeadStart();
+ }
+ _rlp = abi.encodePacked(_rlp, temp1, _bh.logsBloom.encodeBytes());
+ bytes memory temp2;
+ // SPR struct could be an empty struct
+ // In that case, serialize(SPR) = 0xF800
+ if (_bh.isSPREmpty) {
+ temp2 = emptyListHeadStart();
+ } else {
+ // patchReceiptHash and receiptHash might be empty
+ // In that case, encoding these two items gives the result as 0xF800
+ if (_bh.spr.patchReceiptHash.length != 0) {
+ temp1 = _bh.spr.patchReceiptHash.encodeBytes();
+ } else {
+ temp1 = emptyListHeadStart();
+ }
+ temp2 = abi.encodePacked(_bh.spr.stateHash.encodeBytes(), temp1);
+
+ if (_bh.spr.receiptHash.length != 0) {
+ temp1 = _bh.spr.receiptHash.encodeBytes();
+ } else {
+ temp1 = emptyListHeadStart();
+ }
+ temp2 = abi.encodePacked(temp2, temp1);
+ temp2 = abi
+ .encodePacked(addLength(temp2.length, false), temp2)
+ .encodeBytes();
+ }
+ _rlp = abi.encodePacked(_rlp, temp2);
+
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeVotes(Types.Votes memory _vote)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+
+ // First, serialize an array of TS
+ for (uint256 i = 0; i < _vote.ts.length; i++) {
+ temp = abi.encodePacked(
+ _vote.ts[i].timestamp.encodeUint(),
+ _vote.ts[i].signature.encodeBytes()
+ );
+ _rlp = abi.encodePacked(_rlp, addLength(temp.length, false), temp);
+ }
+
+ // Next, serialize the blockPartSetID
+ temp = abi.encodePacked(
+ _vote.blockPartSetID.n.encodeUint(),
+ _vote.blockPartSetID.b.encodeBytes()
+ );
+ // Combine all of them
+ _rlp = abi.encodePacked(
+ _vote.round.encodeUint(),
+ addLength(temp.length, false),
+ temp,
+ addLength(_rlp.length, false),
+ _rlp
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeBlockWitness(Types.BlockWitness memory _bw)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+ for (uint256 i = 0; i < _bw.witnesses.length; i++) {
+ temp = _bw.witnesses[i].encodeBytes();
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi.encodePacked(
+ _bw.height.encodeUint(),
+ addLength(_rlp.length, false),
+ _rlp
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeEventProof(Types.EventProof memory _ep)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp;
+ bytes memory temp;
+ for (uint256 i = 0; i < _ep.eventMptNode.length; i++) {
+ temp = _ep.eventMptNode[i].encodeBytes();
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi
+ .encodePacked(addLength(_rlp.length, false), _rlp)
+ .encodeBytes();
+
+ _rlp = abi.encodePacked(_ep.index.encodeUint(), _rlp);
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeBlockUpdate(Types.BlockUpdate memory _bu)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory temp;
+ bytes memory _rlp;
+ // In the case that _validators[] is an empty array, loop will be skipped
+ // and RLP_ENCODE([bytes]) == EMPTY_LIST_HEAD_START (0xF800) instead
+ if (_bu.validators.length != 0) {
+ for (uint256 i = 0; i < _bu.validators.length; i++) {
+ temp = _bu.validators[i].encodeBytes();
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi
+ .encodePacked(addLength(_rlp.length, false), _rlp)
+ .encodeBytes();
+ } else {
+ _rlp = emptyListHeadStart();
+ }
+
+ _rlp = abi.encodePacked(
+ _bu.bh.encodeBlockHeader().encodeBytes(),
+ _bu.votes.encodeVotes().encodeBytes(),
+ _rlp
+ );
+
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeReceiptProof(Types.ReceiptProof memory _rp)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory temp;
+ bytes memory _rlp;
+ // Serialize [bytes] which are transaction receipts
+ for (uint256 i = 0; i < _rp.txReceipts.length; i++) {
+ temp = _rp.txReceipts[i].encodeBytes();
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi
+ .encodePacked(addLength(_rlp.length, false), _rlp)
+ .encodeBytes();
+
+ bytes memory eventProof;
+ for (uint256 i = 0; i < _rp.ep.length; i++) {
+ temp = _rp.ep[i].encodeEventProof();
+ eventProof = abi.encodePacked(eventProof, temp);
+ }
+ _rlp = abi.encodePacked(
+ _rp.index.encodeUint(),
+ _rlp,
+ addLength(eventProof.length, false),
+ eventProof
+ );
+
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeBlockProof(Types.BlockProof memory _bp)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory _rlp =
+ abi.encodePacked(
+ _bp.bh.encodeBlockHeader().encodeBytes(),
+ _bp.bw.encodeBlockWitness().encodeBytes()
+ );
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ function encodeRelayMessage(Types.RelayMessage memory _rm)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ bytes memory temp;
+ bytes memory _rlp;
+ if (_rm.buArray.length != 0) {
+ for (uint256 i = 0; i < _rm.buArray.length; i++) {
+ temp = _rm.buArray[i].encodeBlockUpdate().encodeBytes();
+ _rlp = abi.encodePacked(_rlp, temp);
+ }
+ _rlp = abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ } else {
+ _rlp = emptyListShortStart();
+ }
+
+ if (_rm.isBPEmpty == false) {
+ temp = _rm.bp.encodeBlockProof();
+ } else {
+ temp = emptyListHeadStart();
+ }
+ _rlp = abi.encodePacked(_rlp, temp);
+
+ bytes memory receiptProof;
+ if (_rm.isRPEmpty == false) {
+ for (uint256 i = 0; i < _rm.rp.length; i++) {
+ temp = _rm.rp[i].encodeReceiptProof().encodeBytes();
+ receiptProof = abi.encodePacked(receiptProof, temp);
+ }
+ receiptProof = abi.encodePacked(
+ addLength(receiptProof.length, false),
+ receiptProof
+ );
+ } else {
+ receiptProof = emptyListShortStart();
+ }
+ _rlp = abi.encodePacked(_rlp, receiptProof);
+
+ return abi.encodePacked(addLength(_rlp.length, false), _rlp);
+ }
+
+ // Adding LIST_HEAD_START by length
+ // There are two cases:
+ // 1. List contains less than or equal 55 elements (total payload of the RLP) -> LIST_HEAD_START = LIST_SHORT_START + [0-55] = [0xC0 - 0xF7]
+ // 2. List contains more than 55 elements:
+ // - Total Payload = 512 elements = 0x0200
+ // - Length of Total Payload = 2
+ // => LIST_HEAD_START = \x (LIST_LONG_START + length of Total Payload) \x (Total Payload) = \x(F7 + 2) \x(0200) = \xF9 \x0200 = 0xF90200
+ function addLength(uint256 length, bool isLongList)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ if (length > 55 && !isLongList) {
+ bytes memory payLoadSize = RLPEncode.encodeUintByLength(length);
+ return
+ abi.encodePacked(
+ addLength(payLoadSize.length, true),
+ payLoadSize
+ );
+ } else if (length <= 55 && !isLongList) {
+ return abi.encodePacked(uint8(LIST_SHORT_START + length));
+ }
+ return abi.encodePacked(uint8(LIST_LONG_START + length));
+ }
+
+ function emptyListHeadStart() internal pure returns (bytes memory) {
+ bytes memory payLoadSize = RLPEncode.encodeUintByLength(0);
+ return
+ abi.encodePacked(
+ abi.encodePacked(uint8(LIST_LONG_START + payLoadSize.length)),
+ payLoadSize
+ );
+ }
+
+ function emptyListShortStart() internal pure returns (bytes memory) {
+ return abi.encodePacked(LIST_SHORT_START);
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/String.sol b/solidity/nativecoinERC20/contracts/libraries/String.sol
new file mode 100644
index 00000000..55ce075c
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/String.sol
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+/**
+ * String Library
+ *
+ * This is a simple library of string functions which try to simplify
+ * string operations in solidity.
+ *
+ * Please be aware some of these functions can be quite gas heavy so use them only when necessary
+ *
+ * The original library was modified. If you want to know more about the original version
+ * please check this link: https://github.com/willitscale/solidity-util.git
+ */
+library String {
+ /**
+ * splitBTPAddress
+ *
+ * Split the BTP Address format i.e. btp://1234.iconee/0x123456789
+ * into Network_address (1234.iconee) and Server_address (0x123456789)
+ *
+ * @param _base String base BTP Address format to be split
+ * @dev _base must follow a BTP Address format
+ *
+ * @return string, string The resulting strings of Network_address and Server_address
+ */
+ function splitBTPAddress(string memory _base)
+ internal
+ pure
+ returns (string memory, string memory)
+ {
+ string[] memory temp = split(_base, "/");
+ return (temp[2], temp[3]);
+ }
+
+ /**
+ * Concat
+ *
+ * Appends two strings together and returns a new value
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string which will be the concatenated
+ * prefix
+ * @param _value The value to be the concatenated suffix
+ * @return string The resulting string from combinging the base and value
+ */
+ function concat(string memory _base, string memory _value)
+ internal
+ pure
+ returns (string memory)
+ {
+ return string(abi.encodePacked(_base, _value));
+ }
+
+ /**
+ * Index Of
+ *
+ * Locates and returns the position of a character within a string
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string acting as the haystack to be
+ * searched
+ * @param _value The needle to search for, at present this is currently
+ * limited to one character
+ * @return int The position of the needle starting from 0 and returning -1
+ * in the case of no matches found
+ */
+ function indexOf(string memory _base, string memory _value)
+ internal
+ pure
+ returns (int256)
+ {
+ return _indexOf(_base, _value, 0);
+ }
+
+ /**
+ * Index Of
+ *
+ * Locates and returns the position of a character within a string starting
+ * from a defined offset
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string acting as the haystack to be
+ * searched
+ * @param _value The needle to search for, at present this is currently
+ * limited to one character
+ * @param _offset The starting point to start searching from which can start
+ * from 0, but must not exceed the length of the string
+ * @return int The position of the needle starting from 0 and returning -1
+ * in the case of no matches found
+ */
+ function _indexOf(
+ string memory _base,
+ string memory _value,
+ uint256 _offset
+ ) internal pure returns (int256) {
+ bytes memory _baseBytes = bytes(_base);
+ bytes memory _valueBytes = bytes(_value);
+
+ assert(_valueBytes.length == 1);
+
+ for (uint256 i = _offset; i < _baseBytes.length; i++) {
+ if (_baseBytes[i] == _valueBytes[0]) {
+ return int256(i);
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Length
+ *
+ * Returns the length of the specified string
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string to be measured
+ * @return uint The length of the passed string
+ */
+ function length(string memory _base) internal pure returns (uint256) {
+ bytes memory _baseBytes = bytes(_base);
+ return _baseBytes.length;
+ }
+
+ /*
+ * String Split (Very high gas cost)
+ *
+ * Splits a string into an array of strings based off the delimiter value.
+ * Please note this can be quite a gas expensive function due to the use of
+ * storage so only use if really required.
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string value to be split.
+ * @param _value The delimiter to split the string on which must be a single
+ * character
+ * @return string[] An array of values split based off the delimiter, but
+ * do not container the delimiter.
+ */
+ function split(string memory _base, string memory _value)
+ internal
+ pure
+ returns (string[] memory splitArr)
+ {
+ bytes memory _baseBytes = bytes(_base);
+
+ uint256 _offset = 0;
+ uint256 _splitsCount = 1;
+ while (_offset < _baseBytes.length - 1) {
+ int256 _limit = _indexOf(_base, _value, _offset);
+ if (_limit == -1) break;
+ else {
+ _splitsCount++;
+ _offset = uint256(_limit) + 1;
+ }
+ }
+
+ splitArr = new string[](_splitsCount);
+
+ _offset = 0;
+ _splitsCount = 0;
+ while (_offset < _baseBytes.length - 1) {
+ int256 _limit = _indexOf(_base, _value, _offset);
+ if (_limit == -1) {
+ _limit = int256(_baseBytes.length);
+ }
+
+ string memory _tmp = new string(uint256(_limit) - _offset);
+ bytes memory _tmpBytes = bytes(_tmp);
+
+ uint256 j = 0;
+ for (uint256 i = _offset; i < uint256(_limit); i++) {
+ _tmpBytes[j++] = _baseBytes[i];
+ }
+ _offset = uint256(_limit) + 1;
+ splitArr[_splitsCount++] = string(_tmpBytes);
+ }
+ return splitArr;
+ }
+
+ /**
+ * Compare To
+ *
+ * Compares the characters of two strings, to ensure that they have an
+ * identical footprint
+ *
+ * @param _base When being used for a data type this is the extended object
+ * otherwise this is the string base to compare against
+ * @param _value The string the base is being compared to
+ * @return bool Simply notates if the two string have an equivalent
+ */
+ function compareTo(string memory _base, string memory _value)
+ internal
+ pure
+ returns (bool)
+ {
+ if (
+ keccak256(abi.encodePacked(_base)) ==
+ keccak256(abi.encodePacked(_value))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ function toString(uint256 _i) internal pure returns (string memory) {
+ if (_i == 0) {
+ return "0";
+ }
+ uint256 j = _i;
+ uint256 len;
+ while (j != 0) {
+ len++;
+ j /= 10;
+ }
+ bytes memory bstr = new bytes(len);
+ uint256 k = len - 1;
+ while (_i != 0) {
+ bstr[k--] = byte(uint8(48 + (_i % 10)));
+ _i /= 10;
+ }
+ return string(bstr);
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/libraries/Types.sol b/solidity/nativecoinERC20/contracts/libraries/Types.sol
new file mode 100644
index 00000000..4e48e5f1
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/libraries/Types.sol
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+library Types {
+ /**
+ * @Notice List of ALL Struct being used to Encode and Decode RLP Messages
+ */
+
+ // SPR = State Hash + Pathch Receipt Hash + Receipt Hash
+ struct SPR {
+ bytes stateHash;
+ bytes patchReceiptHash;
+ bytes receiptHash;
+ }
+
+ struct BlockHeader {
+ uint256 version;
+ uint256 height;
+ uint256 timestamp;
+ bytes proposer;
+ bytes prevHash;
+ bytes voteHash;
+ bytes nextValidators;
+ bytes patchTxHash;
+ bytes txHash;
+ bytes logsBloom;
+ SPR spr;
+ bool isSPREmpty; // add to check whether SPR is an empty struct
+ // It will not be included in serializing thereafter
+ }
+
+ // TS = Timestamp + Signature
+ struct TS {
+ uint256 timestamp;
+ bytes signature;
+ }
+
+ // BPSI = blockPartSetID
+ struct BPSI {
+ uint256 n;
+ bytes b;
+ }
+
+ struct Votes {
+ uint256 round;
+ BPSI blockPartSetID;
+ TS[] ts;
+ }
+
+ struct BlockWitness {
+ uint256 height;
+ bytes[] witnesses;
+ }
+
+ struct EventProof {
+ uint256 index;
+ bytes[] eventMptNode;
+ }
+
+ struct BlockUpdate {
+ BlockHeader bh;
+ Votes votes;
+ bytes[] validators;
+ }
+
+ struct ReceiptProof {
+ uint256 index;
+ bytes[] txReceipts;
+ EventProof[] ep;
+ }
+
+ struct BlockProof {
+ BlockHeader bh;
+ BlockWitness bw;
+ }
+
+ struct RelayMessage {
+ BlockUpdate[] buArray;
+ BlockProof bp;
+ bool isBPEmpty; // add to check in a case BlockProof is an empty struct
+ // when RLP RelayMessage, this field will not be serialized
+ ReceiptProof[] rp;
+ bool isRPEmpty; // add to check in a case ReceiptProof is an empty struct
+ // when RLP RelayMessage, this field will not be serialized
+ }
+
+ /**
+ * @Notice List of ALL Structs being used by a BSH contract
+ */
+ enum ServiceType {
+ REQUEST_COIN_TRANSFER,
+ REQUEST_COIN_REGISTER,
+ REPONSE_HANDLE_SERVICE,
+ UNKNOWN_TYPE
+ }
+
+ struct PendingTransferCoin {
+ string from;
+ string to;
+ string[] coinNames;
+ uint256[] amounts;
+ uint256[] fees;
+ }
+
+ struct TransferCoin {
+ string from;
+ string to;
+ Asset[] assets;
+ }
+
+ struct Asset {
+ string coinName;
+ uint256 value;
+ }
+
+ struct AssetTransferDetail {
+ string coinName;
+ uint256 value;
+ uint256 fee;
+ }
+
+ struct Response {
+ uint256 code;
+ string message;
+ }
+
+ struct ServiceMessage {
+ ServiceType serviceType;
+ bytes data;
+ }
+
+ struct Coin {
+ uint256 id;
+ string symbol;
+ uint256 decimals;
+ }
+
+ struct Balance {
+ uint256 lockedBalance;
+ uint256 refundableBalance;
+ }
+
+ struct Request {
+ string serviceName;
+ address bsh;
+ }
+
+ /**
+ * @Notice List of ALL Structs being used by a BMC contract
+ */
+
+ struct VerifierStats {
+ uint256 heightMTA; // MTA = Merkle Trie Accumulator
+ uint256 offsetMTA;
+ uint256 lastHeight; // Block height of last verified message which is BTP-Message contained
+ bytes extra;
+ }
+
+ struct Service {
+ string svc;
+ address addr;
+ }
+
+ struct Verifier {
+ string net;
+ address addr;
+ }
+
+ struct Route {
+ string dst; // BTP Address of destination BMC
+ string next; // BTP Address of a BMC before reaching dst BMC
+ }
+
+ struct Link {
+ address[] relays; // Address of multiple Relays handle for this link network
+ uint256 rxSeq;
+ uint256 txSeq;
+ uint256 blockIntervalSrc;
+ uint256 blockIntervalDst;
+ uint256 maxAggregation;
+ uint256 delayLimit;
+ uint256 relayIdx;
+ uint256 rotateHeight;
+ uint256 rxHeight;
+ uint256 rxHeightSrc;
+ bool isConnected;
+ }
+
+ struct LinkStats {
+ uint256 rxSeq;
+ uint256 txSeq;
+ VerifierStats verifier;
+ RelayStats[] relays;
+ uint256 relayIdx;
+ uint256 rotateHeight;
+ uint256 rotateTerm;
+ uint256 delayLimit;
+ uint256 maxAggregation;
+ uint256 rxHeightSrc;
+ uint256 rxHeight;
+ uint256 blockIntervalSrc;
+ uint256 blockIntervalDst;
+ uint256 currentHeight;
+ }
+
+ struct RelayStats {
+ address addr;
+ uint256 blockCount;
+ uint256 msgCount;
+ }
+
+ struct BMCMessage {
+ string src; // an address of BMC (i.e. btp://1234.PARA/0x1234)
+ string dst; // an address of destination BMC
+ string svc; // service name of BSH
+ int256 sn; // sequence number of BMC
+ bytes message; // serializef Service Message from BSH
+ }
+
+ struct Connection {
+ string from;
+ string to;
+ }
+
+ struct EventMessage {
+ string eventType;
+ Connection conn;
+ }
+
+ struct BMCService {
+ string serviceType;
+ bytes payload;
+ }
+
+ struct GatherFeeMessage {
+ string fa; // BTP address of Fee Aggregator
+ string[] svcs; // a list of services
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/BMC.sol b/solidity/nativecoinERC20/contracts/test/BMC.sol
new file mode 100644
index 00000000..8f977373
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/BMC.sol
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "../interfaces/IBSH.sol";
+import "../interfaces/IBMCPeriphery.sol";
+import "../interfaces/IBMV.sol";
+import "../libraries/ParseAddress.sol";
+import "../libraries/RLPEncodeStruct.sol";
+import "../libraries/RLPDecodeStruct.sol";
+import "../libraries/String.sol";
+import "../libraries/EncodeBase64.sol";
+import "../libraries/DecodeBase64.sol";
+import "../libraries/Types.sol";
+
+contract BMC is IBMCPeriphery {
+ using ParseAddress for address;
+ using ParseAddress for string;
+ using RLPDecodeStruct for bytes;
+ using RLPEncodeStruct for Types.BMCMessage;
+ using RLPEncodeStruct for Types.ServiceMessage;
+ using RLPEncodeStruct for Types.EventMessage;
+ using RLPEncodeStruct for Types.Response;
+ using String for string;
+
+ uint256 internal constant RC_OK = 0;
+ uint256 internal constant RC_ERR = 1;
+
+ event Message(
+ string _next, // an address of the next BMC (it could be a destination BMC)
+ uint256 _seq, // a sequence number of BMC (NOT sequence number of BSH)
+ bytes _msg
+ );
+
+ // emit EVENT to announce link/unlink service
+ event Event(string _next, uint256 _seq, bytes _msg);
+
+ event ErrorOnBTPError(
+ string _svc,
+ int256 _sn,
+ uint256 _code,
+ string _errMsg,
+ uint256 _svcErrCode,
+ string _svcErrMsg
+ );
+
+ mapping(address => bool) private _owners;
+ uint256 private numOfOwner;
+
+ mapping(string => address) internal bshServices;
+ mapping(string => address) private bmvServices;
+ mapping(string => string) private connectedBMC;
+ mapping(address => Types.RelayStats) private relayStats;
+ mapping(string => string) private routes;
+ mapping(string => Types.Link) internal links; // should be private, temporarily set internal for testing
+ mapping(string => string[]) private reachable;
+ string[] private listBMVNames;
+ string[] private listBSHNames;
+ string[] private listRouteKeys;
+ string[] private listLinkNames;
+ address[] private addrs;
+
+ string public bmcAddress; // a network address BMV, i.e. btp://1234.pra
+ uint256 private numOfBMVService;
+ uint256 private numOfBSHService;
+ uint256 private numOfLinks;
+ uint256 private numOfRoutes;
+
+ uint256 private constant BLOCK_INTERVAL_MSEC = 1000;
+ uint256 internal constant UNKNOWN_ERR = 0;
+ uint256 internal constant BMC_ERR = 10;
+ uint256 internal constant BMV_ERR = 25;
+ uint256 internal constant BSH_ERR = 40;
+ uint256 private constant DECIMAL_PRECISION = 10**6;
+
+ modifier owner {
+ require(_owners[msg.sender] == true, "BMCRevertUnauthorized");
+ _;
+ }
+
+ constructor(string memory _network) {
+ bmcAddress = string("btp://").concat(_network).concat("/").concat(
+ address(this).toString()
+ );
+ _owners[msg.sender] = true;
+ numOfOwner++;
+ }
+
+ /*****************************************************************************************
+
+ *****************************************************************************************/
+ function getBmcBtpAddress() external view override returns (string memory) {
+ return bmcAddress;
+ }
+
+ function handleRelayMessage(string calldata _prev, string calldata _msg)
+ external
+ override
+ {}
+
+ function handleMessage(string calldata _prev, Types.BMCMessage memory _msg)
+ internal
+ {
+ if (_msg.svc.compareTo("bmc")) {
+ Types.BMCService memory _sm;
+ try this.tryDecodeBMCService(_msg.message) returns (
+ Types.BMCService memory ret
+ ) {
+ _sm = ret;
+ } catch {
+ _sendError(_prev, _msg, BMC_ERR, "BMCRevertParseFailure");
+ }
+ if (_sm.serviceType.compareTo("FeeGathering")) {
+ Types.GatherFeeMessage memory _gatherFee;
+ try this.tryDecodeGatherFeeMessage(_sm.payload) returns (
+ Types.GatherFeeMessage memory ret
+ ) {
+ _gatherFee = ret;
+ } catch {
+ _sendError(_prev, _msg, BMC_ERR, "BMCRevertParseFailure");
+ }
+ for (uint256 i = 0; i < _gatherFee.svcs.length; i++) {
+ // If 'svc' not found, ignore
+ if (bshServices[_gatherFee.svcs[i]] != address(0)) {
+ try
+ IBSH(bshServices[_gatherFee.svcs[i]])
+ .handleFeeGathering(
+ _gatherFee.fa,
+ _gatherFee.svcs[i]
+ )
+ {} catch {
+ // If BSH contract throws a revert error, ignore and continue
+ }
+ }
+ }
+ }
+ } else {
+ if (bshServices[_msg.svc] == address(0)) {
+ _sendError(_prev, _msg, BMC_ERR, "BMCRevertNotExistsBSH");
+ return;
+ }
+
+ if (_msg.sn >= 0) {
+ (string memory _net, ) = _msg.src.splitBTPAddress();
+ try
+ IBSH(bshServices[_msg.svc]).handleBTPMessage(
+ _net,
+ _msg.svc,
+ uint256(_msg.sn),
+ _msg.message
+ )
+ {} catch Error(string memory _error) {
+ _sendError(_prev, _msg, BSH_ERR, _error);
+ }
+ } else {
+ Types.Response memory _errMsg = _msg.message.decodeResponse();
+ try
+ IBSH(bshServices[_msg.svc]).handleBTPError(
+ _msg.src,
+ _msg.svc,
+ uint256(int256(_msg.sn) * -1),
+ _errMsg.code,
+ _errMsg.message
+ )
+ {} catch Error(string memory _error) {
+ emit ErrorOnBTPError(
+ _msg.svc,
+ int256(_msg.sn) * -1,
+ _errMsg.code,
+ _errMsg.message,
+ BSH_ERR,
+ _error
+ );
+ } catch (bytes memory _error) {
+ emit ErrorOnBTPError(
+ _msg.svc,
+ int256(_msg.sn) * -1,
+ _errMsg.code,
+ _errMsg.message,
+ UNKNOWN_ERR,
+ string(_error)
+ );
+ }
+ }
+ }
+ }
+
+ // @dev Solidity does not allow using try_catch with internal function
+ // Thus, work-around solution is the followings
+ // If there is any error throwing, BMC contract can catch it, then reply back a RC_ERR Response
+ function tryDecodeBMCService(bytes calldata _msg)
+ external
+ pure
+ returns (Types.BMCService memory)
+ {
+ return _msg.decodeBMCService();
+ }
+
+ function tryDecodeGatherFeeMessage(bytes calldata _msg)
+ external
+ pure
+ returns (Types.GatherFeeMessage memory)
+ {
+ return _msg.decodeGatherFeeMessage();
+ }
+
+ function _sendMessage(string memory _to, bytes memory _serializedMsg)
+ internal
+ {
+ links[_to].txSeq += 1;
+ emit Message(_to, links[_to].txSeq, _serializedMsg);
+ }
+
+ function _sendError(
+ string calldata _prev,
+ Types.BMCMessage memory _message,
+ uint256 _errCode,
+ string memory _errMsg
+ ) internal {
+ if (_message.sn > 0) {
+ bytes memory _serializedMsg =
+ Types
+ .BMCMessage(
+ bmcAddress,
+ _message
+ .src,
+ _message
+ .svc,
+ int256(_message.sn) * -1,
+ Types
+ .ServiceMessage(
+ Types
+ .ServiceType
+ .REPONSE_HANDLE_SERVICE,
+ Types.Response(_errCode, _errMsg).encodeResponse()
+ )
+ .encodeServiceMessage()
+ )
+ .encodeBMCMessage();
+ _sendMessage(_prev, _serializedMsg);
+ }
+ }
+
+ /**
+ @notice Send the message to a specific network.
+ @dev Caller must be an registered BSH.
+ @param _to Network Address of destination network
+ @param _svc Name of the service
+ @param _sn Serial number of the message, it should be positive
+ @param _msg Serialized bytes of Service Message
+ */
+ function sendMessage(
+ string memory _to,
+ string memory _svc,
+ uint256 _sn,
+ bytes memory _msg
+ ) external override {
+ require(
+ msg.sender == address(this) || bshServices[_svc] == msg.sender,
+ "BMCRevertUnauthorized"
+ );
+ require(_sn >= 0, "BMCRevertInvalidSN");
+ // In case BSH sends a REQUEST_COIN_TRANSFER,
+ // but '_to' is a network which is not supported by BMC
+ // revert() therein
+ if (bmvServices[_to] == address(0)) {
+ revert("BMCRevertNotExistsBMV");
+ }
+ string memory _toBMC = connectedBMC[_to];
+ bytes memory _rlp =
+ Types
+ .BMCMessage(bmcAddress, _toBMC, _svc, int256(_sn), _msg)
+ .encodeBMCMessage();
+ if (_svc.compareTo("_EVENT")) {
+ links[_toBMC].txSeq += 1;
+ emit Event(_toBMC, links[_toBMC].txSeq, _rlp);
+ } else {
+ links[_toBMC].txSeq += 1;
+ emit Message(_toBMC, links[_toBMC].txSeq, _rlp);
+ }
+ }
+
+ /**
+ @notice Add the smart contract for the service.
+ @dev Caller must be an operator of BTP network.
+ @param _svc Name of the service
+ @param _addr Service's contract address
+ */
+ function addService(string memory _svc, address _addr) external owner {
+ require(_addr != address(0), "BMCRevertInvalidAddress");
+ require(bshServices[_svc] == address(0), "BMCRevertAlreadyExistsBSH");
+ bshServices[_svc] = _addr;
+ listBSHNames.push(_svc);
+ }
+
+ /**
+ @notice Registers BMV for the network.
+ @dev Caller must be an operator of BTP network.
+ @param _net Network Address of the blockchain
+ @param _addr Address of BMV
+ */
+ function addVerifier(string memory _net, address _addr) external owner {
+ require(bmvServices[_net] == address(0), "BMCRevertAlreadyExistsBMV");
+ bmvServices[_net] = _addr;
+ listBMVNames.push(_net);
+ numOfBMVService++;
+ }
+
+ /**
+ @notice Initializes status information for the link.
+ @dev Caller must be an operator of BTP network.
+ @param _link BTP Address of connected BMC
+ */
+ function addLink(string calldata _link) external owner {
+ string memory _net;
+ (_net, ) = _link.splitBTPAddress();
+ require(bmvServices[_net] != address(0), "BMCRevertNotExistsBMV");
+ require(
+ links[_link].isConnected == false,
+ "BMCRevertAlreadyExistsLink"
+ );
+ links[_link] = Types.Link(
+ new address[](0),
+ 0,
+ 0,
+ BLOCK_INTERVAL_MSEC,
+ 0,
+ 10,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ true
+ );
+ listLinkNames.push(_link);
+ connectedBMC[_net] = _link;
+ numOfLinks++;
+
+ // propagate an event "LINK"
+ propagateEvent("Link", _link);
+ }
+
+ function propagateEvent(string memory _eventType, string calldata _link)
+ private
+ {}
+
+ /*
+ @notice Get status of BMC.
+ @param _link BTP Address of the connected BMC.
+ @return tx_seq Next sequence number of the next sending message.
+ @return rx_seq Next sequence number of the message to receive.
+ @return verifier VerifierStatus Object contains status information of the BMV.
+ */
+ function getStatus(string calldata _link)
+ external
+ view
+ override
+ returns (Types.LinkStats memory _linkStats)
+ {
+ _linkStats.txSeq = links[_link].txSeq;
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/CheckParseAddress.sol b/solidity/nativecoinERC20/contracts/test/CheckParseAddress.sol
new file mode 100644
index 00000000..b319482e
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/CheckParseAddress.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "../libraries/ParseAddress.sol";
+
+contract CheckParseAddress {
+ using ParseAddress for address;
+ using ParseAddress for string;
+
+ function convertAddressToString(address _addr)
+ external
+ pure
+ returns (string memory strAddr)
+ {
+ strAddr = _addr.toString();
+ }
+
+ function convertStringToAddress(string calldata _addr)
+ external
+ pure
+ returns (address addr)
+ {
+ addr = _addr.parseAddress();
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/EncodeMessage.sol b/solidity/nativecoinERC20/contracts/test/EncodeMessage.sol
new file mode 100644
index 00000000..2dc4a2b2
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/EncodeMessage.sol
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "../libraries/ParseAddress.sol";
+import "../libraries/RLPEncodeStruct.sol";
+import "../libraries/RLPDecodeStruct.sol";
+import "../libraries/String.sol";
+import "../libraries/Types.sol";
+
+contract EncodeMessage {
+ using RLPEncodeStruct for Types.BMCMessage;
+ using RLPEncodeStruct for Types.ServiceMessage;
+ using RLPEncodeStruct for Types.TransferCoin;
+ using RLPEncodeStruct for Types.Response;
+ using RLPEncodeStruct for Types.GatherFeeMessage;
+ using RLPEncodeStruct for Types.BMCService;
+ using RLPEncodeStruct for string[];
+ using ParseAddress for address;
+ using ParseAddress for string;
+ using String for string;
+
+ function encodeTransferFeesBMCMessage(
+ string memory _fromBMC,
+ string memory _toBMC,
+ string memory _toAddress,
+ string memory _svc,
+ int256 _sn,
+ address _bsh,
+ Types.Asset[] memory _fees
+ ) external pure returns (bytes memory) {
+ (, string memory _to) = _toAddress.splitBTPAddress();
+ return
+ Types
+ .BMCMessage(
+ _fromBMC,
+ _toBMC,
+ _svc,
+ _sn,
+ encodeServiceMessage(_bsh.toString(), _to, _fees)
+ )
+ .encodeBMCMessage();
+ }
+
+ function encodeBMCService(string calldata _fa, string[] memory _svcs)
+ external
+ pure
+ returns (bytes memory)
+ {
+ return
+ Types
+ .BMCService(
+ "FeeGathering",
+ Types.GatherFeeMessage(_fa, _svcs).encodeGatherFeeMessage()
+ )
+ .encodeBMCService();
+ }
+
+ function encodeResponseBMCMessage(
+ string memory _from,
+ string memory _to,
+ string memory _svc,
+ int256 _sn,
+ uint256 _code,
+ string memory _msg
+ ) external view returns (bytes memory) {
+ return
+ Types
+ .BMCMessage(
+ _from,
+ _to,
+ _svc,
+ _sn,
+ this.encodeResponseMsg(
+ Types.ServiceType.REPONSE_HANDLE_SERVICE,
+ _code,
+ _msg
+ )
+ )
+ .encodeBMCMessage();
+ }
+
+ function hashCoinName(string memory _coinName)
+ external
+ pure
+ returns (uint256)
+ {
+ return uint256(keccak256(abi.encodePacked(_coinName)));
+ }
+
+ function encodeResponseMsg(
+ Types.ServiceType _serviceType,
+ uint256 _code,
+ string calldata _msg
+ ) external pure returns (bytes memory) {
+ return
+ Types
+ .ServiceMessage(
+ _serviceType,
+ Types.Response(_code, _msg).encodeResponse()
+ )
+ .encodeServiceMessage();
+ }
+
+ function encodeBatchTransferMsgWithAddress(
+ string calldata _from,
+ address _to,
+ Types.Asset[] memory _assets
+ ) external pure returns (bytes memory) {
+ return encodeServiceMessage(_from, _to.toString(), _assets);
+ }
+
+ function encodeBatchTransferMsgWithStringAddress(
+ string calldata _from,
+ string calldata _to,
+ Types.Asset[] memory _assets
+ ) external pure returns (bytes memory) {
+ return encodeServiceMessage(_from, _to, _assets);
+ }
+
+ function encodeTransferMsgWithAddress(
+ string calldata _from,
+ address _to,
+ string memory _coinName,
+ uint256 _value
+ ) external pure returns (bytes memory) {
+ Types.Asset[] memory asset = new Types.Asset[](1);
+ asset[0] = Types.Asset(_coinName, _value);
+ return encodeServiceMessage(_from, _to.toString(), asset);
+ }
+
+ function encodeTransferMsgWithStringAddress(
+ string calldata _from,
+ string calldata _to,
+ string calldata _coinName,
+ uint256 _value
+ ) external pure returns (bytes memory) {
+ Types.Asset[] memory asset = new Types.Asset[](1);
+ asset[0] = Types.Asset(_coinName, _value);
+ return encodeServiceMessage(_from, _to, asset);
+ }
+
+ function encodeServiceMessage(
+ string memory _from,
+ string memory _to,
+ Types.Asset[] memory _asset
+ ) private pure returns (bytes memory) {
+ return
+ Types
+ .ServiceMessage(
+ Types
+ .ServiceType
+ .REQUEST_COIN_TRANSFER,
+ Types.TransferCoin(_from, _to, _asset).encodeTransferCoinMsg()
+ )
+ .encodeServiceMessage();
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/Holder.sol b/solidity/nativecoinERC20/contracts/test/Holder.sol
new file mode 100644
index 00000000..14ed74e3
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/Holder.sol
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol";
+import "../interfaces/IBSHPeriphery.sol";
+import "../interfaces/IBSHCore.sol";
+import "../libraries/String.sol";
+
+contract Holder is ERC1155Holder {
+ IBSHPeriphery private bshp;
+ IBSHCore private bshc;
+ using String for string;
+
+ function deposit() external payable {}
+
+ function addBSHContract(address _bshp, address _bshc) external {
+ bshp = IBSHPeriphery(_bshp);
+ bshc = IBSHCore(_bshc);
+ }
+
+ function callTransfer(
+ string calldata _coinName,
+ uint256 _value,
+ string calldata _to
+ ) external {
+ bshc.transfer(_coinName, _value, _to);
+ }
+
+ // function isSendingNative(string[] memory _coinNames)
+ // private
+ // pure
+ // returns (int256)
+ // {
+ // for (uint256 i = 0; i < _coinNames.length; i++) {
+ // if (_coinNames[i].compareTo("PARA")) {
+ // return int256(i);
+ // }
+ // }
+ // return -1;
+ // }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/MockBMC.sol b/solidity/nativecoinERC20/contracts/test/MockBMC.sol
new file mode 100644
index 00000000..9f9eff1d
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/MockBMC.sol
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "./BMC.sol";
+
+contract MockBMC is BMC {
+ constructor(string memory _network) BMC(_network) {}
+
+ function receiveRequest(
+ string calldata _src,
+ string memory _dst,
+ string memory _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external {
+ handleMessage(
+ _src,
+ Types.BMCMessage(_src, _dst, _svc, int256(_sn), _msg)
+ );
+ }
+
+ function receiveResponse(
+ string calldata _from,
+ string memory _svc,
+ uint256 _sn,
+ bytes calldata _msg
+ ) external {
+ IBSH(bshServices[_svc]).handleBTPMessage(_from, _svc, _sn, _msg);
+ }
+
+ function getBalance(address _addr) external view returns (uint256) {
+ return _addr.balance;
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/MockBSHCore.sol b/solidity/nativecoinERC20/contracts/test/MockBSHCore.sol
new file mode 100644
index 00000000..848f4806
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/MockBSHCore.sol
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "../BSHCore.sol";
+
+contract MockBSHCore is BSHCore {
+ function mintMock(
+ address _acc,
+ uint256 _id,
+ uint256 _value
+ ) external {
+ _mint(_acc, _value);
+ }
+
+ function burnMock(
+ address _acc,
+ uint256 _id,
+ uint256 _value
+ ) external {
+ _burn(_acc, _value);
+ }
+
+ function setAggregationFee(string calldata _coinName, uint256 _value)
+ external
+ {
+ aggregationFee[_coinName] += _value;
+ }
+
+ function clearAggregationFee() external {
+ for (uint256 i = 0; i < coinsName.length; i++) {
+ delete aggregationFee[coinsName[i]];
+ }
+ }
+
+ function clearBSHPerifSetting() external {
+ bshPeriphery = IBSHPeriphery(address(0));
+ }
+
+ function setRefundableBalance(
+ address _acc,
+ string calldata _coinName,
+ uint256 _value
+ ) external {
+ balances[_acc][_coinName].refundableBalance += _value;
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/MockBSHPerif.sol b/solidity/nativecoinERC20/contracts/test/MockBSHPerif.sol
new file mode 100644
index 00000000..8fe1a50f
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/MockBSHPerif.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+pragma experimental ABIEncoderV2;
+import "../BSHPeriphery.sol";
+import "../BSHCore.sol";
+
+contract MockBSHPeriphery is BSHPeriphery {
+ using String for string;
+
+ function getFees(uint256 _sn)
+ external
+ view
+ returns (Types.PendingTransferCoin memory)
+ {
+ return requests[_sn];
+ }
+
+ function getAggregationFeeOf(string calldata _coinName)
+ external
+ view
+ returns (uint256 _fee)
+ {
+ Types.Asset[] memory _fees = bshCore.getAccumulatedFees();
+ for (uint256 i = 0; i < _fees.length; i++) {
+ if (_coinName.compareTo(_fees[i].coinName)) return _fees[i].value;
+ }
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/NonRefundable.sol b/solidity/nativecoinERC20/contracts/test/NonRefundable.sol
new file mode 100644
index 00000000..27308a66
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/NonRefundable.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+// This contract does not have any method
+// that allows to receives coins, i.e. receive() external payable/fallback() external payable
+// Instead, it has a method deposit() payable to receive coins
+contract NonRefundable {
+ function deposit() external payable {}
+
+ function transfer(
+ address _bsh,
+ string calldata _to,
+ uint256 _amt
+ ) external {
+ (bool success, bytes memory err) =
+ _bsh.call{value: _amt}(
+ abi.encodeWithSignature("transferNativeCoin(string)", _to)
+ );
+ require(success, string(err));
+ }
+}
diff --git a/solidity/nativecoinERC20/contracts/test/NotPayable.sol b/solidity/nativecoinERC20/contracts/test/NotPayable.sol
new file mode 100644
index 00000000..cb4d6d77
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/NotPayable.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+// This contract does not have any method
+// that allows to receives coins, i.e. receive() external payable/fallback() external payable
+contract NotPayable {
+
+}
diff --git a/solidity/nativecoinERC20/contracts/test/Refundable.sol b/solidity/nativecoinERC20/contracts/test/Refundable.sol
new file mode 100644
index 00000000..79db438a
--- /dev/null
+++ b/solidity/nativecoinERC20/contracts/test/Refundable.sol
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity >=0.5.0 <0.8.0;
+
+// This contract does not have any method
+// that allows to receives coins, i.e. receive() external payable/fallback() external payable
+// Instead, it has a method deposit() payable to receive coins
+contract Refundable {
+ function deposit() external payable {}
+
+ function transfer(
+ address _bsh,
+ string calldata _to,
+ uint256 _amt
+ ) external {
+ (bool success, bytes memory err) =
+ _bsh.call{value: _amt}(
+ abi.encodeWithSignature("transferNativeCoin(string)", _to)
+ );
+ require(success, string(err));
+ }
+
+ receive() external payable {}
+}
diff --git a/solidity/nativecoinERC20/migrations/1_deploy_bsh.js b/solidity/nativecoinERC20/migrations/1_deploy_bsh.js
new file mode 100644
index 00000000..d372eba9
--- /dev/null
+++ b/solidity/nativecoinERC20/migrations/1_deploy_bsh.js
@@ -0,0 +1,12 @@
+const BSHPeriphery = artifacts.require("BSHPeriphery");
+const BSHCore = artifacts.require("BSHCore");
+const { deployProxy } = require('@openzeppelin/truffle-upgrades');
+
+module.exports = async function (deployer, network) {
+ if (network !== "development") {
+ await deployProxy(BSHCore, [process.env.BSH_COIN_URL, process.env.BSH_COIN_NAME, parseInt(process.env.BSH_COIN_FEE), parseInt(process.env.BSH_FIXED_FEE)], { deployer });
+ await deployProxy(BSHPeriphery, [process.env.BMC_PERIPHERY_ADDRESS, BSHCore.address, process.env.BSH_SERVICE], { deployer });
+ const bshCore = await BSHCore.deployed();
+ await bshCore.updateBSHPeriphery(BSHPeriphery.address);
+ }
+};
diff --git a/solidity/nativecoinERC20/package.json b/solidity/nativecoinERC20/package.json
new file mode 100644
index 00000000..d6a1930c
--- /dev/null
+++ b/solidity/nativecoinERC20/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "nativecoin-erc20",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@openzeppelin/contracts": "^3.4.1-solc-0.7-2",
+ "@openzeppelin/contracts-upgradeable": "^3.4.1-solc-0.7-2",
+ "@openzeppelin/truffle-upgrades": "^1.7.0",
+ "@truffle/hdwallet-provider": "^1.4.1"
+ },
+ "devDependencies": {
+ "chai": "^4.3.4",
+ "husky": "^6.0.0",
+ "prettier": "^2.2.1",
+ "prettier-plugin-solidity": "^1.0.0-beta.7",
+ "rlp": "^2.2.6",
+ "solhint": "^3.3.4",
+ "solhint-plugin-prettier": "^0.0.5",
+ "truffle-assertions": "^0.9.2"
+ },
+ "scripts": {
+ "linter": "./node_modules/.bin/solhint -f table ./contracts/**/*.sol -f table ./contracts/*.sol",
+ "prettier": "./node_modules/.bin/prettier --write ./contracts -l",
+ "contract:compile": "truffle compile --all",
+ "test": "yarn test:unit && yarn test:integration",
+ "test:unit": "rm -rf .openzeppelin && truffle test test/unit/*.js",
+ "test:integration": "rm -rf .openzeppelin && truffle test test/integration/*.js"
+ },
+ "husky": {
+ "hooks": {
+ "pre-push": "yarn linter && yarn prettier"
+ }
+ }
+}
diff --git a/solidity/nativecoinERC20/test/integration/2-native-coin-bsh.js b/solidity/nativecoinERC20/test/integration/2-native-coin-bsh.js
new file mode 100644
index 00000000..51566f5b
--- /dev/null
+++ b/solidity/nativecoinERC20/test/integration/2-native-coin-bsh.js
@@ -0,0 +1,328 @@
+const MockBSHPeriphery = artifacts.require("MockBSHPeriphery");
+const BSHPeriphery = artifacts.require("BSHPeriphery");
+const BSHCore = artifacts.require("BSHCore");
+const BMC = artifacts.require("MockBMC");
+const Holder = artifacts.require("Holder");
+const NotPayable = artifacts.require("NotPayable");
+const NonRefundable = artifacts.require("NonRefundable");
+const Refundable = artifacts.require("Refundable");
+const EncodeMsg = artifacts.require("EncodeMessage");
+const { assert, AssertionError } = require('chai');
+const truffleAssert = require('truffle-assertions');
+const rlp = require('rlp');
+
+let toHex = (buf) => {
+ buf = buf.toString('hex');
+ if (buf.substring(0, 2) == '0x')
+ return buf;
+ return '0x' + buf.toString('hex');
+};
+
+contract('As a user, I want to send MOVR to ICON blockchain', (accounts) => {
+ let bsh_perif, bsh_core, bmc, nonrefundable, refundable;
+ let service = 'Coin/WrappedCoin'; let _bmcICON = 'btp://1234.iconee/0x1234567812345678';
+ let _net = '1234.iconee'; let _to = 'btp://1234.iconee/0x12345678';
+ let RC_OK = 0; let RC_ERR = 1;
+ let _native = 'MOVR'; let deposit = 1000000000000;
+ let _fee = 10; let _fixed_fee = 500000;
+ let REPONSE_HANDLE_SERVICE = 2; let _uri = 'https://github.com/icon-project/btp';
+ let _tokenName = 'ICX';
+ let _tokenSymbol = 'ICX';
+ let _initialSupply = 1;
+
+ let DECIMALS = 18;
+ let INITIAL_SUPPLY = web3.utils.toBN(100000) // 100000 tokens
+ before(async () => {
+ bsh_perif = await BSHPeriphery.new();
+ bsh_core = await BSHCore.new();
+ bmc = await BMC.new('1234.movr');
+ encode_msg = await EncodeMsg.new();
+ await bsh_perif.initialize(bmc.address, bsh_core.address, service);
+ await bsh_core.initialize(_native, _fee, _fixed_fee, _tokenName, _tokenSymbol, INITIAL_SUPPLY);
+ await bsh_core.updateBSHPeriphery(bsh_perif.address);
+ nonrefundable = await NonRefundable.new();
+ refundable = await Refundable.new();
+ await bmc.addService(service, bsh_perif.address);
+ await bmc.addVerifier(_net, accounts[1]);
+ await bmc.addLink(_bmcICON);
+ });
+
+ it('Scenario 5: Account client transfers a valid native coin to a side chain', async () => {
+ let amount = 600000;
+ let account_balanceBefore = await bsh_core.getBalanceOf(accounts[0], _native);
+ let tx = await bsh_core.transferNativeCoin(_to, { from: accounts[0], value: amount });
+ let account_balanceAfter = await bsh_core.getBalanceOf(accounts[0], _native);
+ let bsh_coin_balance = await bsh_core.getBalanceOf(bsh_core.address, _native);
+ let chargedFee = Math.floor(amount / 1000) + _fixed_fee;
+
+ const transferEvents = await bsh_perif.getPastEvents('TransferStart', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ let event = transferEvents[0].returnValues;
+ assert.equal(event._from, accounts[0]);
+ assert.equal(event._to, _to);
+ assert.equal(event._sn, 1);
+ assert.equal(event._assetDetails.length, 1);
+ assert.equal(event._assetDetails[0].coinName, 'MOVR');
+ assert.equal(event._assetDetails[0].value, amount - chargedFee);
+ assert.equal(event._assetDetails[0].fee, chargedFee);
+
+ const linkStatus = await bmc.getStatus(_bmcICON);
+ const bmcBtpAddress = await bmc.getBmcBtpAddress();
+
+ const messageEvents = await bmc.getPastEvents('Message', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ event = messageEvents[0].returnValues;
+ assert.equal(event._next, _bmcICON);
+ assert.equal(event._seq, linkStatus.txSeq);
+
+ const bmcMsg = rlp.decode(event._msg);
+
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[0])), bmcBtpAddress);
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[1])), _bmcICON);
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[2])), service);
+ assert.equal(web3.utils.hexToNumber(toHex(bmcMsg[3])), 1);
+
+ const ServiceMsg = rlp.decode(bmcMsg[4]);
+ assert.equal(web3.utils.hexToUtf8(toHex(ServiceMsg[0])), 0);
+
+ const coinTransferMsg = rlp.decode(ServiceMsg[1]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[0])), accounts[0]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[1])), _to.split('/').slice(-1)[0]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[2][0][0])), _native);
+ assert.equal(web3.utils.hexToNumber(toHex(coinTransferMsg[2][0][1])), amount - chargedFee);
+
+ assert(
+ web3.utils.BN(bsh_coin_balance._usableBalance).toNumber() === amount &&
+ web3.utils.BN(account_balanceBefore._lockedBalance).toNumber() === 0 &&
+ web3.utils.BN(account_balanceAfter._lockedBalance).toNumber() === amount
+ );
+ });
+
+ it('Scenario 6: BSHPeriphery receives a successful response of a recent request', async () => {
+ let amount = 600000;
+ let account_balanceBefore = await bsh_core.getBalanceOf(accounts[0], _native);
+ let _msg = await encode_msg.encodeResponseMsg(REPONSE_HANDLE_SERVICE, RC_OK, "");
+ let tx = await bmc.receiveResponse(_net, service, 1, _msg);
+ let account_balanceAfter = await bsh_core.getBalanceOf(accounts[0], _native);
+ let fees = await bsh_core.getAccumulatedFees();
+
+ const transferEvents = await bsh_perif.getPastEvents('TransferEnd', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ let event = transferEvents[0].returnValues;
+
+ assert.equal(event._from, accounts[0]);
+ assert.equal(event._sn, 1);
+ assert.equal(event._code, 0);
+ assert.equal(event._response, '');
+
+ assert(
+ fees[0].coinName === _native &&
+ Number(fees[0].value) === (Math.floor(amount / 1000) + _fixed_fee) &&
+ web3.utils.BN(account_balanceBefore._lockedBalance).toNumber() === amount &&
+ web3.utils.BN(account_balanceAfter._lockedBalance).toNumber() === 0
+ );
+ });
+});
+
+contract('As a user, I want to send ERC20_ICX to ICON blockchain', (accounts) => {
+ let bsh_perif, bsh_core, bmc, holder;
+ let service = 'Coin/WrappedCoin';
+ let _native = 'MOVR'; let _fee = 10; let _fixed_fee = 500000; let _bmcICON = 'btp://1234.iconee/0x1234567812345678';
+ let _net = '1234.iconee'; let _from = '0x12345678'; let _value = 999999999999999;
+ let REPONSE_HANDLE_SERVICE = 2; let RC_OK = 0; let RC_ERR = 1;
+ let _tokenName = 'ICX';
+ let _tokenSymbol = 'ICX';
+ let INITIAL_SUPPLY = web3.utils.toBN(10000000000000) // 100000 tokens
+
+ before(async () => {
+ bsh_perif = await BSHPeriphery.new();
+ bsh_core = await BSHCore.new();
+ bmc = await BMC.new('1234.movr');
+ encode_msg = await EncodeMsg.new();
+ await bsh_perif.initialize(bmc.address, bsh_core.address, service);
+ await bsh_core.initialize(_native, _fee, _fixed_fee, _tokenName, _tokenSymbol, INITIAL_SUPPLY);
+ await bsh_core.updateBSHPeriphery(bsh_perif.address);
+ holder = await Holder.new();
+ await bmc.addService(service, bsh_perif.address);
+ await bmc.addVerifier(_net, accounts[1]);
+ await bmc.addLink(_bmcICON);
+ await holder.addBSHContract(bsh_perif.address, bsh_core.address);
+ let _msg = await encode_msg.encodeTransferMsgWithAddress(_from, holder.address, _tokenName, _value);
+ await bmc.receiveRequest(_bmcICON, "", service, 0, _msg);
+ let balanceBefore = await bsh_core.balanceOf(accounts[0]);
+ });
+
+ it('Scenario 8: User sends a valid transferring request', async () => {
+ let _to = 'btp://1234.iconee/0x12345678';
+ let amount = 600000;
+ await bsh_core.transfer(accounts[1], amount);
+ let balanceBefore = await bsh_core.getBalanceOf(accounts[1], _tokenName);
+ await bsh_core.approve(bsh_core.address, amount, { from: accounts[1] });
+ const data = await bsh_core.contract.methods["transfer(string,uint256,string)"](_tokenName, amount, _to).encodeABI();
+ let tx = await bsh_core.sendTransaction({ data, from: accounts[1] });
+ //let tx = await bsh_core.transfer(_tokenName, amount, _to, {from: accounts[1]});
+ let balanceAfter = await bsh_core.getBalanceOf(accounts[1], _tokenName);
+ let bsh_core_balance = await bsh_core.getBalanceOf(bsh_core.address, _tokenName);
+ let chargedFee = Math.floor(amount / 1000) + _fixed_fee;
+
+ const transferEvents = await bsh_perif.getPastEvents('TransferStart', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ let event = transferEvents[0].returnValues;
+ assert.equal(event._from, accounts[1]);
+ assert.equal(event._to, _to);
+ assert.equal(event._sn, 1);
+ assert.equal(event._assetDetails.length, 1);
+ assert.equal(event._assetDetails[0].coinName, _tokenName);
+ assert.equal(event._assetDetails[0].value, amount - chargedFee);
+ assert.equal(event._assetDetails[0].fee, chargedFee);
+
+ const linkStatus = await bmc.getStatus(_bmcICON);
+ const bmcBtpAddress = await bmc.getBmcBtpAddress();
+
+ const messageEvents = await bmc.getPastEvents('Message', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ event = messageEvents[0].returnValues;
+ assert.equal(event._next, _bmcICON);
+ assert.equal(event._seq, linkStatus.txSeq);
+
+ const bmcMsg = rlp.decode(event._msg);
+
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[0])), bmcBtpAddress);
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[1])), _bmcICON);
+ assert.equal(web3.utils.hexToUtf8(toHex(bmcMsg[2])), service);
+ assert.equal(web3.utils.hexToNumber(toHex(bmcMsg[3])), 1);
+
+ const ServiceMsg = rlp.decode(bmcMsg[4]);
+ assert.equal(web3.utils.hexToUtf8(toHex(ServiceMsg[0])), 0);
+
+ const coinTransferMsg = rlp.decode(ServiceMsg[1]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[0])), accounts[1]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[1])), _to.split('/').slice(-1)[0]);
+ assert.equal(web3.utils.hexToUtf8(toHex(coinTransferMsg[2][0][0])), _tokenName);
+ assert.equal(web3.utils.hexToNumber(toHex(coinTransferMsg[2][0][1])), amount - chargedFee);
+
+ assert.equal(web3.utils.BN(balanceBefore._lockedBalance).toNumber(), 0);
+ assert.equal(web3.utils.BN(balanceAfter._lockedBalance).toNumber(), amount);
+ assert.equal(
+ web3.utils.BN(balanceAfter._usableBalance).toNumber(),
+ web3.utils.BN(balanceBefore._usableBalance).toNumber() - amount
+ );
+ assert.equal(web3.utils.BN(bsh_core_balance._usableBalance).toNumber(), amount);
+ });
+
+ it('Scenario 9: BSHPeriphery receives a successful response of a recent request', async () => {
+ let amount = 600000;
+ let chargedFee = Math.floor(amount / 1000) + _fixed_fee;
+ let contract_balanceBefore = await bsh_core.getBalanceOf(accounts[1], _tokenName);
+ let _msg = await encode_msg.encodeResponseMsg(REPONSE_HANDLE_SERVICE, RC_OK, "");
+ let tx = await bmc.receiveResponse(_net, service, 1, _msg);
+ let contract_balanceAfter = await bsh_core.getBalanceOf(accounts[1], _tokenName);
+ let fees = await bsh_core.getAccumulatedFees();
+ let bsh_core_balance = await bsh_core.getBalanceOf(bsh_core.address, _tokenName);
+
+ const transferEvents = await bsh_perif.getPastEvents('TransferEnd', { fromBlock: tx.receipt.blockNumber, toBlock: 'latest' });
+ let event = transferEvents[0].returnValues;
+
+ assert.equal(event._from, accounts[1]);
+ assert.equal(event._sn, 1);
+ assert.equal(event._code, 0);
+ assert.equal(event._response, '');
+
+ assert.equal(web3.utils.BN(contract_balanceBefore._lockedBalance).toNumber(), amount);
+ assert.equal(web3.utils.BN(contract_balanceAfter._lockedBalance).toNumber(), 0);
+ assert.equal(
+ web3.utils.BN(contract_balanceBefore._usableBalance).toNumber(),
+ web3.utils.BN(contract_balanceAfter._usableBalance).toNumber()
+ );
+ assert.equal(web3.utils.BN(bsh_core_balance._usableBalance).toNumber(), chargedFee);
+ assert.equal(fees[1].coinName, _tokenName);//todo: check this
+ assert.equal(Number(fees[1].value), chargedFee)
+ });
+});
+
+contract('As a user, I want to receive MOVR from ICON blockchain', (accounts) => {
+ let bmc, bsh_perif, bsh_core, notpayable, refundable;
+ let service = 'Coin/WrappedCoin'; let _bmcICON = 'btp://1234.iconee/0x1234567812345678';
+ let _net = '1234.iconee'; let _to = 'btp://1234.iconee/0x12345678';
+ let _native = 'MOVR'; let _fee = 10; let _fixed_fee = 500000;
+ let RC_ERR = 1; let RC_OK = 0;
+ let _uri = 'https://github.com/icon-project/btp';
+ let _tokenName = 'ICX';
+ let _tokenSymbol = 'ICX';
+ let INITIAL_SUPPLY = web3.utils.toBN(10000000000000) // 100000 tokens
+
+ before(async () => {
+ bsh_perif = await BSHPeriphery.new();
+ bsh_core = await BSHCore.new();
+ bmc = await BMC.new('1234.movr');
+ encode_msg = await EncodeMsg.new();
+ await bsh_perif.initialize(bmc.address, bsh_core.address, service);
+ await bsh_core.initialize(_native, _fee, _fixed_fee, _tokenName, _tokenSymbol, INITIAL_SUPPLY);
+ await bsh_core.updateBSHPeriphery(bsh_perif.address);
+ notpayable = await NotPayable.new();
+ refundable = await Refundable.new();
+ await bmc.addService(service, bsh_perif.address);
+ await bmc.addVerifier(_net, accounts[1]);
+ await bmc.addLink(_bmcICON);
+ await bsh_core.transferNativeCoin(_to, { from: accounts[0], value: 100000000 });
+ btpAddr = await bmc.bmcAddress();
+ });
+
+ it('Scenario 4: BSHPeriphery receives a request of transferring coins', async () => {
+ let _from = '0x12345678';
+ let _value = 12345;
+ let balanceBefore = await bmc.getBalance(accounts[1]);
+ let _eventMsg = await encode_msg.encodeResponseBMCMessage(btpAddr, _bmcICON, service, 10, RC_OK, '');
+ let _msg = await encode_msg.encodeTransferMsgWithAddress(_from, accounts[1], _native, _value);
+ let output = await bmc.receiveRequest(_bmcICON, '', service, 10, _msg);
+ let balanceAfter = await bmc.getBalance(accounts[1]);
+
+ assert.equal(
+ web3.utils.BN(balanceAfter).toString(),
+ web3.utils.BN(balanceBefore).add(new web3.utils.BN(_value)).toString()
+ );
+ assert.equal(output.logs[0].args._next, _bmcICON);
+ assert.equal(output.logs[0].args._msg, _eventMsg);
+ });
+});
+
+contract('As a user, I want to receive ERC20_ICX from ICON blockchain', (accounts) => {
+ let bmc, bsh_perif, bsh_core, holder, notpayable;
+ let service = 'Coin/WrappedCoin'; let _uri = 'https://github.com/icon-project/btp';
+ let _native = 'MOVR'; let _fee = 10; let _fixed_fee = 500000;
+ let _name = 'ICON'; let _bmcICON = 'btp://1234.iconee/0x1234567812345678';
+ let _net = '1234.iconee'; let _from = '0x12345678';
+ let RC_ERR = 1; let RC_OK = 0;
+ let _tokenName = 'ICX';
+ let _tokenSymbol = 'ICX';
+ let INITIAL_SUPPLY = web3.utils.toBN(10000000000000) // 100000 tokens
+
+
+ before(async () => {
+ bsh_perif = await BSHPeriphery.new();
+ bsh_core = await BSHCore.new();
+ bmc = await BMC.new('1234.movr');
+ encode_msg = await EncodeMsg.new();
+ await bsh_perif.initialize(bmc.address, bsh_core.address, service);
+ await bsh_core.initialize(_native, _fee, _fixed_fee, _tokenName, _tokenSymbol, INITIAL_SUPPLY);
+ await bsh_core.updateBSHPeriphery(bsh_perif.address);
+ holder = await Holder.new();
+ notpayable = await NotPayable.new();
+ await bmc.addService(service, bsh_perif.address);
+ await bmc.addVerifier(_net, accounts[1]);
+ await bmc.addLink(_bmcICON);
+ await holder.addBSHContract(bsh_perif.address, bsh_core.address);
+ btpAddr = await bmc.bmcAddress();
+ });
+
+ it('Scenario 5: Receiver is an account client', async () => {
+ let _value = 5500;
+ let balanceBefore = await bsh_core.balanceOf(accounts[1]);
+ let _eventMsg = await encode_msg.encodeResponseBMCMessage(btpAddr, _bmcICON, service, 10, RC_OK, '');
+ let _msg = await encode_msg.encodeTransferMsgWithAddress(_from, accounts[1], _tokenName, _value);
+ let output = await bmc.receiveRequest(_bmcICON, '', service, 10, _msg);
+ let balanceAfter = await bsh_core.balanceOf(accounts[1]);
+
+ assert.equal(
+ web3.utils.BN(balanceAfter).toNumber(),
+ web3.utils.BN(balanceBefore).toNumber() + _value
+ );
+ assert.equal(output.logs[0].args._next, _bmcICON);
+ assert.equal(output.logs[0].args._msg, _eventMsg);
+ });
+});
\ No newline at end of file
diff --git a/solidity/nativecoinERC20/test/integration/native-coin-bsh b/solidity/nativecoinERC20/test/integration/native-coin-bsh
new file mode 100644
index 00000000..19ba07a8
--- /dev/null
+++ b/solidity/nativecoinERC20/test/integration/native-coin-bsh
@@ -0,0 +1,33 @@
+const BSHPerif = artifacts.require("BSHPeriphery");
+const MockBSHCore = artifacts.require("MockBSHCore");
+const CheckParseAddress = artifacts.require("CheckParseAddress");
+const { assert } = require('chai');
+const truffleAssert = require('truffle-assertions');
+
+
+// BSHPeriphery is being used for communications among BSHCore and BMCPeriphery contract
+// Thus, all tests relating to BSHPeriphery will be moved to Integration Test
+// This part just covers some basic feature which is checking an authorization
+contract('BSHPeriphery Unit Tests', (accounts) => {
+ let bsh_perif;
+
+ before(async () => {
+ bsh_perif = await BSHPerif.new();
+ });
+
+
+ it('Scenario 40: Should succeed when a client, which owns a refundable, tries to reclaim', async () => {
+ let _coin = 'ICON'; let _value = 10000;
+ let _id = await bsh_core.coinId(_coin);
+ await bsh_core.mintMock(bsh_core.address, _id, _value);
+ let balanceBefore = await bsh_core.getBalanceOf(accounts[2], _coin);
+ await bsh_core.reclaim(_coin, _value, {from: accounts[2]});
+ let balanceAfter = await bsh_core.getBalanceOf(accounts[2], _coin);
+ assert(
+ web3.utils.BN(balanceAfter._usableBalance).toNumber() ===
+ web3.utils.BN(balanceBefore._usableBalance).toNumber() + _value
+ );
+ });
+
+});
+
\ No newline at end of file
diff --git a/solidity/nativecoinERC20/truffle-config.js b/solidity/nativecoinERC20/truffle-config.js
new file mode 100644
index 00000000..f5352c5e
--- /dev/null
+++ b/solidity/nativecoinERC20/truffle-config.js
@@ -0,0 +1,59 @@
+
+const HDWalletProvider = require("@truffle/hdwallet-provider");
+
+var privKeys = [
+ "1deb607f38b0bd1390df3b312a1edc11a00a34f248b5d53f4157de054f3c71ae",
+ "a1617c7e1691ee5691d0c750125e96a2630f75ef8e87cdd87f363c00d42163e7",
+ "3d5f8ff132c7f10a03e138b952e556976707725c9aae98e4ed3df6172b8aaa4f",
+ "fd52d799e21ad6d35a4e0c1679fd82eecbe3e3ccfdeceb8a1eed3a742423f688"
+]
+
+module.exports = {
+ networks: {
+ testnet: {
+ provider: () => new HDWalletProvider(privKeys,
+ `http://127.0.0.1:9545`),
+ network_id: 1000,
+ confirmations: 0,
+ timeoutBlocks: 900,
+ skipDryRun: true
+ },
+ bscLocal: {
+ networkCheckTimeout: 10000,
+ provider: () => new HDWalletProvider(privKeys, "ws://localhost:8546",
+ ),
+ network_id: '97',
+ confirmations: 0,
+ skipDryRun: true,
+ gas: 8000000
+ },
+ bscDocker: {
+ provider: () => new HDWalletProvider({
+ privateKeys: privKeys,
+ providerOrUrl: "BSC_RPC_URI",
+ chainId: 97,
+ }),
+ network_id: '97',
+ skipDryRun: true,
+ networkCheckTimeout: 1000000000
+ },
+ development: {
+ host: "localhost",
+ port: 9545,
+ network_id: "*", // Match any network id
+ }
+ },
+ compilers: {
+ solc: {
+ version: "0.7.6",
+ settings: {
+ optimizer: {
+ enabled: true, // Default: false
+ runs: 200 // Default: 200
+ },
+ evmVersion: "petersburg"
+ }
+ }
+ },
+ plugins: ["truffle-contract-size"]
+};