diff --git a/neo/How_to_become_neo_relayer_cn.md b/neo/How_to_become_neo_relayer_cn.md new file mode 100644 index 0000000..d4d554f --- /dev/null +++ b/neo/How_to_become_neo_relayer_cn.md @@ -0,0 +1,40 @@ +# neo-relayer + +[English](./How_to_become_neo_relayer_en.md) | 中文 + +## 架构 + +一方面,neo-relayer实现了对Neo网络的监听,会持续扫描每个Neo区块并将切换了下一轮共识地址的关键区块头同步到中继链上,同时能识别并转发跨链交易,向中继链提交区块的状态根和交易的梅克尔证明。 +另一方面,neo-relayer也实现了对中继链的监听,会持续扫描每个中继链区块并将切换了共识地址的关键区块头同步到Neo链上,中继链收到其他链的跨链信息会构造到Neo链的返回交易,neo-relayer会将这些交易所在的中继链区块头同步到Neo链上因为其中包含了状态根,同时转发这些交易的梅克尔证明及跨链信息。 +只要存在一个neo-relayer在工作,那么整个Neo跨链生态就可以持续运作。 + +## 准备 + +1. 申请中继链钱包 +申请一个中继链的钱包,如果有本体的钱包可以直接使用,二者钱包是通用的。 + +2. 注册Relayer +然后向中继链注册自己的地址为Relayer。 + +## 启动Relayer + +1. 下载neo-relayer的可执行文件,解压至特定位置 +2. 更改配置信息,例如: + +```json +{ + "RelayJsonRpcUrl": "http://138.91.6.125:40336", // 中继链rpc地址 + "RelayChainID": 0, // 中继链编码 + "WalletFile": "./wallet.dat", // 中继链钱包 + "NeoWalletFile": "TBD", // 待定,后续版本可能采用钱包文件以替代钱包wif + "NeoWalletWIF": "L3Hab7wL43SbWLnkfnVCp6dT99xzfB4qLZxeR9dFgcWWPirwKyXp", // Neo钱包wif + "NeoJsonRpcUrl": "http://47.89.240.111:11332", // Neo链rpc地址 + "NeoChainID": 4, // Neo链编码 + "NeoCCMC": "b0d4f20da68a6007d4fb7eac374b5566a5b0e229", // Neo链的跨链管理合约 + "ScanInterval": 2, // 扫描中继链的时间间隔,单位为秒 + "GasPrice": 0, + "GasLimit": 200000 +} +``` + +3. 输入./main --loglevel 0 --cliconfig “path” 执行Relayer可执行文件以运行neo-relayer diff --git a/neo/How_to_become_neo_relayer_en.md b/neo/How_to_become_neo_relayer_en.md new file mode 100644 index 0000000..59a621d --- /dev/null +++ b/neo/How_to_become_neo_relayer_en.md @@ -0,0 +1,45 @@ +# neo-relayer + +English | [中文](./How_to_become_neo_relayer_CN.md) + +## Structure + +On one hand, neo-relayer monitors Neo network continuously, scans each Neo block and synchronizes those key block headers which contains different "NextConsensus" field to the relay chain. At the same time, neo-relayer can recognize cross-chain transactions, and provides the "StateRoot" of the block containing those transactions together with the "MerkleProof" of those transactions to the relay chain. +On the other hand, neo-relayer also monitors the relay chain, scans each relay chain block and synchronizes those key block headers which has changed next consensus addresses to Neo chain. Moreover, the relay chain creates cross-chain transactions to Neo chain when it receives cross-chain transactions from other chains. Then neo-relayer relays the "StateRoot" of the block containing those transactions together with the "MerkleProof" of those transactions to Neo chain. +The entire Neo cross-chain ecosystem can continue to function normally even with just one active neo-relayer. + +## Setup + +1. Create a relay chain wallet +Create a wallet to be used on the relay chain. If you have an existing Ontology wallet it can be used on the relay chain. The two wallets are mutually interchangeable. + +2. Register the neo-relayer +Register your address as the **relayer** on the relay chain. + +## Start Up + +1. Download the executable file of the relayer and extract it to a specific directory. + +2. Modify the configuration settings. Here is a sample configuration: + +```json +{ + "RelayJsonRpcUrl": "http://138.91.6.125:40336", // the rpc address of the relay chain + "RelayChainID": 0, // the relay chain id + "WalletFile": "./wallet.dat", // the wallet used on the relay chain + "NeoWalletFile": "TBD", // to be determined, future versions may replace WIF with wallet file of Neo network + "NeoWalletWIF": "L3Hab7wL43SbWLnkfnVCp6dT99xzfB4qLZxeR9dFgcWWPirwKyXp", // private key in WIF format + "NeoJsonRpcUrl": "http://47.89.240.111:11332", // the rpc address of the Neo chain + "NeoChainID": 4, // Neo chain id + "NeoCCMC": "b0d4f20da68a6007d4fb7eac374b5566a5b0e229", // cross chain management contract of Neo chain + "ScanInterval": 2, // the time interval to scan the relay chain, in seconds + "GasPrice": 0, + "GasLimit": 200000 +} +``` + +3. Use the following command with the configuration file path to enable the relayer: + +```bash +./main --loglevel 0 --cliconfig "path" +``` diff --git a/neo/How_to_cross_NEP5_asset_cn.md b/neo/How_to_cross_NEP5_asset_cn.md new file mode 100644 index 0000000..49b9da2 --- /dev/null +++ b/neo/How_to_cross_NEP5_asset_cn.md @@ -0,0 +1,44 @@ +# 如何将NEP5资产跨到其他链上 + +[English](How_to_cross_NEP5_asset_en.md) | 中文 + +## 收集资产在链上对应的合约地址 + +以某个NEP5资产为例,这种资产在链上的合约地址为: + +-Neo链: 4da43995ee75be3931fd3abae06a3e6447d2febf (小端序) + +-Ethereum链: 0x86B7Be11D77043E5aDe3858f2F694E4f89d4a941 + +## 执行跨链操作 + +### 集成跨链功能的NEP5资产 + +如果该资产集成了跨链的Lock和Unlock接口,则可以直接调用该资产的Lock接口进行跨链资产转移: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**参数设置:** + +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (Neo链地址APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR的script hash,小端序) +- toChainId: 2 (Ethereum链的编码) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (Ethereum链地址) +- amount: 10000 (跨链数量) + +### 未集成跨链功能的NEP5资产 + +如果该资产未集成跨链的Lock和Unlock接口,则需要调用该资产对应的代理合约的Lock接口: + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**参数设置:** + +- fromAssetHash: 4da43995ee75be3931fd3abae06a3e6447d2febf (NEP5资产的script hash,小端序) +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (Neo链地址APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR的script hash,小端序) +- toChainId: 2 (Ethereum链的编码) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (Ethereum链地址) +- amount: 10000 (跨链数量) diff --git a/neo/How_to_cross_NEP5_asset_en.md b/neo/How_to_cross_NEP5_asset_en.md new file mode 100644 index 0000000..544f569 --- /dev/null +++ b/neo/How_to_cross_NEP5_asset_en.md @@ -0,0 +1,44 @@ +# How to Transfer NEP5 Assets to Other Chains + +English | [中文](./How_to_cross_NEP5_asset_cn.md) + +## Collect the corresponding contract addresses for the asset on different chains + +For example, an NEP5 asset has the following contract addresses: + +-Neo chain: 4da43995ee75be3931fd3abae06a3e6447d2febf (little endian) + +-Ethereum chain: 0x86B7Be11D77043E5aDe3858f2F694E4f89d4a941 + +## Start cross chain operation + +### NEP5 assets with cross chain features + +If the asset has embedded the "Lock" and "Unlock" API methods, cross chain transactions can be carried out by invoking the "Lock" method directly: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**Parameters setup:** + +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (script hash of a Neo chain address APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR in little endian) +- toChainId: 2 (Ethereum chain id) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (an address on Ethereum chain) +- amount: 10000 (cross chain amount) + +### NEP5 assets without cross chain features + +If the asset doesn't have the "Lock" and "Unlock" API methods natively, then the "Lock" method of its corresponding proxy contract is invoked to create cross chain transactions: + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**Parameters setup:** + +- fromAssetHash: 4da43995ee75be3931fd3abae06a3e6447d2febf (script hash of the NEP5 asset in little endian) +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (script hash of a Neo chain address APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR in little endian) +- toChainId: 2 (Ethereum chain id) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (an address on Ethereum chain) +- amount: 10000 (cross chain amount) diff --git a/neo/Neo_cross_chain_contract_dev_cn.md b/neo/Neo_cross_chain_contract_dev_cn.md new file mode 100644 index 0000000..3676923 --- /dev/null +++ b/neo/Neo_cross_chain_contract_dev_cn.md @@ -0,0 +1,399 @@ +# Neo跨链合约开发 + +[English](Neo_cross_chain_contract_dev_en.md) | 中文 + +Neo多链生态是基于跨链合约构建的,所谓跨链合约就是合约的逻辑贯穿两条甚至多条链,例如: + +在Neo上发布的某种NEP5资产可以和Ethereum上的某种ERC20资产相对应并互相流转。 + +## 跨链合约概述 + +跨链合约实际上是由多本合约构成的,例如dApp开发者需要在链A和链B上实现跨链业务,此时开发者需要分别在链A和链B上部署智能合约(智能合约A和智能合约B)。 + +跨链合约的开发总的来说可以分为两部分,业务部分和跨链部分: + +业务部分是指在某条链中运行的逻辑代码,按照标准的智能合约开发方式开发,完成合约在该链中的业务。 + +跨链部分主要是指跨链管理合约,当需要跨链时,如链A的逻辑执行完,接下来需要执行链B的逻辑,那么就需要用到跨链管理合约中的跨链接口。 + +## 跨链接口 + +合约的跨链对开发者来说只需要关注一个跨链接口,也就是跨链管理合约的`CrossChain`接口,该接口将链A上已经执行的业务存入梅克尔树,会有矿工生成该跨链交易的梅克尔证明,并将其提交到链B的跨链管理合约中,该跨链管理合约会验证梅克尔证明,并按照参数调用智能合约B中对应的方法。 + +## 跨链合约开发示例(新发行的NEP5资产) + +某开发者想在链A和链B上发行资产,但是希望链A和链B上的资产互通,也就是说其需要发行一种资产,这种资产能够在链A和链B上同时使用,并且可以在链A和链B上自由转移。 + +为了使资产可以在链A和链B之间互相转移,在NEP5标准接口的基础上,需要增加`Lock`和`Unlock`接口,用户在链A中调用`Lock`接口将资产锁定在智能合约A中,该接口同时调用跨链管理合约实现跨链调用智能合约B中的`Unlock`接口,并在链B中将智能合约B中的资产释放给该用户。反之亦然。 + +`Lock`接口实现: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (!IsAddress(fromAddress)) + { + Runtime.Notify("The parameter fromAddress SHOULD be a legal address."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + // more checks + if (!Runtime.CheckWitness(fromAddress)) + { + Runtime.Notify("Authorization failed."); + return false; + } + if (IsPaused()) + { + Runtime.Notify("The contract is paused."); + return false; + } + + // lock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(fromAddress).AsBigInteger(); + if (balance < amount) + { + Runtime.Notify("Not enough balance to lock."); + return false; + } + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + if (totalSupply < amount) + { + Runtime.Notify("Not enough supply to lock."); + return false; + } + asset.Put(fromAddress, balance - amount); + contract.Put("totalSupply", totalSupply - amount); + + // construct args for the corresponding asset contract on target chain + var inputBytes = SerializeArgs(toAddress, amount); + var toContract = GetContractAddress(toChainId); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + var success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + LockEvent(toChainId, fromAddress, toAddress, amount); + return true; +} +``` + +**参数设置:** + +- fromAddress: 跨链调用发起地址,矿工费从该地址扣除 +- toChainId: 目标链的链ID +- toAddress: 目标链上的用户地址 +- amount: 跨链资产数量 + +在上面的代码中可以看到该接口先冻结用户在链A上的需要跨链的数量的资产,并缩减该资产总量(因为该部分资产将锁定而不再流通),然后调用Neo跨链管理合约的`CrossChain`接口,该方法接受四个参数: + +```C# +public static bool CrossChain(BigInteger toChainID, byte[] toChainAddress, byte[] functionName, byte[] args) +``` + +**参数设置:** + +- toChainId: 目标链的链ID +- toChainAddress: 跨链需要调用的目标链上的合约 +- functionName: 跨链资产数量 +- args: 序列化之后的目标合约输入参数 + +在`Lock`接口的代码中可以看到该方法调用了目标合约的`Unlock`接口,`Unlock`接口实现: + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedContract = GetContractAddress(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromContract.AsBigInteger() != storedContract.AsBigInteger()) + { + Runtime.Notify(fromContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedContract); + Runtime.Notify("From contract address not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var toAddress = (byte[])results[0]; + var amount = (BigInteger)results[1]; + if (!IsAddress(toAddress)) + { + Runtime.Notify("ToChain Account address SHOULD be a legal address."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // unlock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(toAddress).AsBigInteger(); + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + asset.Put(toAddress, balance + amount); + contract.Put("totalSupply", totalSupply + amount); + + UnlockEvent(toAddress, amount); + return true; +} +``` + +**参数设置:** + +- inputBytes: 序列化之后的合约参数 +- fromContract: 发起跨链的源链上的合约 +- fromChainId: 源链ID +- caller: 该方法的调用者,不用传入,合约自动补充该参数 + +该接口会先校验调用是不是来自跨链管理合约,然后校验源链上的合约地址是否可信,接着反序列化出所需参数,最后解锁链B上的资产给用户及增加该资产总量。 + +## 跨链合约开发示例(已发行的NEP5资产) + +对于已经存在的NEP5资产来说,其合约代码是不能修改并添加`Lock`、`Unlock`方法的,因此需要额外实现一个代理合约,相当于原先NEP5合约的补充功能,实现了跨链协议中的主要接口,也可以使用现有的代理合约,实现跨链,避免重复开发。 + +以下为代理合约必须实现的接口: + +### BindProxyHash + +```C# +public static bool BindProxyHash(BigInteger toChainId, byte[] targetProxyHash) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap proxyHash = Storage.CurrentContext.CreateMap(nameof(proxyHash)); + proxyHash.Put(toChainId.AsByteArray(), targetProxyHash); + return true; +} +``` + +**参数设置:** + +- toChainId: 目标链的链ID +- targetProxyHash: 跨链需要调用的目标链上的代理合约 + +该接口绑定目标链上的代理合约hash,或者实现了跨链接口的合约hash,在NEP5朝其他链跨链的时候会将该目标链代理合约作为中转站,进而将NEP5转到目标链的映射合约,当然在跨链开始前应该预先部署好映射合约。 + +### BindAssetHash + +```C# +public static bool BindAssetHash(byte[] fromAssetHash, BigInteger toChainId, byte[] toAssetHash, BigInteger initialAmount) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap assetHash = Storage.CurrentContext.CreateMap(nameof(assetHash)); + assetHash.Put(fromAssetHash.Concat(toChainId.AsByteArray()), toAssetHash); + + if (GetAssetBalance(fromAssetHash) != initialAmount) + { + Runtime.Notify("Initial amount incorrect."); + return false; + } + + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + lockedAmount.Put(fromAssetHash, initialAmount); + BindAssetHashEvent(fromAssetHash, toChainId, toAssetHash, initialAmount); + return true; +} +``` + +**参数设置:** + +- fromAssetHash: 源链上的资产合约hash +- toChainId: 目标链的链ID +- toAssetHash: 目标链上的资产合约hash +- initialAmount: 该资产初始锁定数量 + +该接口绑定NEP5代币合约地址和目标链映射合约,比如USDT地址,并设置初始锁定数量。 + +### Lock + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (fromAssetHash.Length != 20) + { + Runtime.Notify("The parameter fromAssetHash SHOULD be 20-byte long."); + return false; + } + if (fromAddress.Length != 20) + { + Runtime.Notify("The parameter fromAddress SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + + // get the corresbonding asset on target chain + var toAssetHash = GetAssetHash(fromAssetHash, toChainId); + if (toAssetHash.Length == 0) + { + Runtime.Notify("Target chain asset hash not found."); + return false; + } + + // get the proxy contract on target chain + var toContract = GetProxyHash(toChainId); + if (toContract.Length == 0) + { + Runtime.Notify("Target chain proxy contract not found."); + return false; + } + + // transfer asset from fromAddress to proxy contract address, use dynamic call to call nep5 token's contract "transfer" + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)fromAssetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { fromAddress, currentHash, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to proxy contract."); + return false; + } + + // construct args for proxy contract on target chain + var inputBytes = SerializeArgs(toAssetHash, toAddress, amount); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(fromAssetHash).ToBigInteger(); + lockedAmount.Put(fromAssetHash, old + amount); + + LockEvent(fromAssetHash, toChainId, toAssetHash, fromAddress, toAddress, amount); + + return true; +} +``` + +**参数设置:** + +- fromAssetHash: 源链上的NEP5资产合约hash +- fromAddress: 跨链调用发起地址,矿工费从该地址扣除 +- toChainId: 目标链的链ID +- toAddress: 目标链上的用户地址 +- amount: 跨链资产数量 + +该接口会把NEP5资产锁定到代理合约账户中,并调用跨链管理合约的`CrossChain`接口从而发出跨链请求。 + +### Unlock + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromProxyContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedProxy = GetProxyHash(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromProxyContract.AsBigInteger() != storedProxy.AsBigInteger()) + { + Runtime.Notify(fromProxyContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedProxy); + Runtime.Notify("From proxy contract not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var assetHash = (byte[])results[0]; + var toAddress = (byte[])results[1]; + var amount = (BigInteger)results[2]; + if (assetHash.Length != 20) + { + Runtime.Notify("ToChain Asset script hash SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length != 20) + { + Runtime.Notify("ToChain Account address SHOULD be 20-byte long."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // transfer asset from proxy contract to toAddress + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)assetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { currentHash, toAddress, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to toAddress."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(assetHash).ToBigInteger(); + lockedAmount.Put(assetHash, old - amount); + + UnlockEvent(assetHash, toAddress, amount); + + return true; +} +``` + +**参数设置:** + +- inputBytes: 序列化之后的合约参数 +- fromProxyContract: 发起跨链的源链上的代理合约hash +- fromChainId: 源链ID +- caller: 该方法的调用者,不用传入,合约自动补充该参数 + +该接口会被跨链管理合约调用,为用户释放原先锁定的NEP5资产,即从其他链返回的NEP5代币。 + +所以代理合约主要实现的是NEP5的注册、锁定与解锁,锁定就是用户将NEP5转到合约中,解锁只有跨链管理合约可以调用。代理合约可以自己部署,也可以使用现有的合约,多个NEP5资产可以使用一本代理合约。 diff --git a/neo/Neo_cross_chain_contract_dev_en.md b/neo/Neo_cross_chain_contract_dev_en.md new file mode 100644 index 0000000..49eee97 --- /dev/null +++ b/neo/Neo_cross_chain_contract_dev_en.md @@ -0,0 +1,397 @@ +# Neo Cross Chain Contract Development + +English | [中文](Neo_cross_chain_contract_dev_cn.md) + +The Neo multi-chain ecosystem is based on cross chain contracts which have the capability to interact with two or more chains. For example: an NEP5 asset released on Neo can be transferred to Ethereum as a corresponding ERC20 asset, or a dApp contract which has some of its business logic taking place on Neo and other carried out on Ethereum. + +## Cross chain contract overview + +The cross chain system actually consists of multiple smart contracts. For example, if a dApp needs to achieve cross chain business logic on Chain A and Chain B, the dApp developers need to deploy smart contract A on Chain A and smart contract B on Chain B. Both smart contracts have part of the dApp business logic. + +Cross chain contract development is composed of two parts: the business logic part and the cross chain supporting part. + +The business logic part is nothing special and should be developed as a conventional smart contract application. It runs its business logic code on the chain where it is deployed. + +The cross chain supporting part is mainly about the cross chain management contract (CCMC). When the business logic is finished on Chain A and the business logic on Chain B is required to be executed, then those interfaces in the CCMC are used. + +## Cross chain interface + +Cross chain contract developers only need to focus on one interface which is the `CrossChain` method in the CCMC. This method stores the business logic execution result on Chain A into a merkle tree. A miner then generates the merkle proof of this cross chain transaction and sends it to the CCMC on Chain B. This CCMC verifies the merkle proof and invokes the corresponding contract methods on Chain B based on the parameters passed by. + +## Cross chain contract development sample (for new released NEP5 asset) + +A developer intends to release a new asset on Chain A and Chain B and wants the asset on both chains are interchangeable which means the asset can be used and transferred between two chains conveniently. + +In order to achieve the interoperability of the asset on both chains, two additional methods `Lock` and `Unlock` are added to the standard NEP5 contracts. The user invokes the `Lock` method on Chain A and the contract locks the cross chain amount of the asset. At the same time, the method then invokes the CCMC on Chain A which invokes the `Unlock` method of the contract on Chain B to release the corresponding amount of asset back to the user, and vice versa. + +Sample `Lock` method implementation in C#: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (!IsAddress(fromAddress)) + { + Runtime.Notify("The parameter fromAddress SHOULD be a legal address."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + // more checks + if (!Runtime.CheckWitness(fromAddress)) + { + Runtime.Notify("Authorization failed."); + return false; + } + if (IsPaused()) + { + Runtime.Notify("The contract is paused."); + return false; + } + + // lock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(fromAddress).AsBigInteger(); + if (balance < amount) + { + Runtime.Notify("Not enough balance to lock."); + return false; + } + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + if (totalSupply < amount) + { + Runtime.Notify("Not enough supply to lock."); + return false; + } + asset.Put(fromAddress, balance - amount); + contract.Put("totalSupply", totalSupply - amount); + + // construct args for the corresponding asset contract on target chain + var inputBytes = SerializeArgs(toAddress, amount); + var toContract = GetContractAddress(toChainId); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + var success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + LockEvent(toChainId, fromAddress, toAddress, amount); + return true; +} +``` + +**Parameters setup:** + +- fromAddress: the user account address starting the cross chain operation where mining fee is deducted +- toChainId: the target chain ID +- toAddress: the user address on target chain +- amount: cross chain asset amount + +In the above code, the method first locks the specified amount of the user's asset on Chain A, reduces the asset total supply (since that amount of asset is locked and not circulating), and then invokes the `CrossChain` of the Neo CCMC which takes four parameters: + +```C# +public static bool CrossChain(BigInteger toChainID, byte[] toChainAddress, byte[] functionName, byte[] args) +``` + +**Parameters setup:** + +- toChainId: the target chain ID +- toChainAddress: the target chain contract address whose `Unlock` method needs to be invoked +- functionName: the method name of the target chain contract, in this case it's "unlock" +- args: the serialized parameters for the target chain contract + +In the above code of `Lock` method, the method name parameter passed to the CCMC is "unlock", so here is the `Unlock` method: + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedContract = GetContractAddress(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromContract.AsBigInteger() != storedContract.AsBigInteger()) + { + Runtime.Notify(fromContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedContract); + Runtime.Notify("From contract address not found."); + return false; + } + + // parse the args bytes constructed in the source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var toAddress = (byte[])results[0]; + var amount = (BigInteger)results[1]; + if (!IsAddress(toAddress)) + { + Runtime.Notify("ToChain Account address SHOULD be a legal address."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // unlock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(toAddress).AsBigInteger(); + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + asset.Put(toAddress, balance + amount); + contract.Put("totalSupply", totalSupply + amount); + + UnlockEvent(toAddress, amount); + return true; +} +``` + +**Parameters setup:** + +- inputBytes: serialized parameters for business logic +- fromContract: the contract on the source chain which invokes the cross chain operation +- fromChainId: the source chain ID +- caller: the caller of this method, no need to pass by, the contract adds this parameter automatically + +This method first checks if it's invoked by the CCMC, then checks the "from" contract parameter is same as what's stored in its storage, deserializes the parameters to run business logic and finally unlocks the user's asset on Chain B and increases the asset total supply. + +## Cross chain contract development sample (for existing NEP5 asset) + +Existing NEP5 assets cannot be modified and implement the `Lock` and `Unlock` methods. So an additional proxy contract is needed to add the cross chain functionality to those NEP5 contracts. Also, off the shelf proxy contracts are ready to be used to avoid duplicate development. + +The proxy contract must implement the following methods: + +### BindProxyHash + +```C# +public static bool BindProxyHash(BigInteger toChainId, byte[] targetProxyHash) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap proxyHash = Storage.CurrentContext.CreateMap(nameof(proxyHash)); + proxyHash.Put(toChainId.AsByteArray(), targetProxyHash); + return true; +} +``` + +**Parameters setup:** + +- toChainId: the target chain ID +- targetProxyHash: the proxy contract hash on target chain + +This method binds the target chain proxy contract hash with chain ID, and acts as the intermerdiary before NEP5 assets are transferred to the token contract on the target chain which is deployed in advance. + +### BindAssetHash + +```C# +public static bool BindAssetHash(byte[] fromAssetHash, BigInteger toChainId, byte[] toAssetHash, BigInteger initialAmount) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap assetHash = Storage.CurrentContext.CreateMap(nameof(assetHash)); + assetHash.Put(fromAssetHash.Concat(toChainId.AsByteArray()), toAssetHash); + + if (GetAssetBalance(fromAssetHash) != initialAmount) + { + Runtime.Notify("Initial amount incorrect."); + return false; + } + + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + lockedAmount.Put(fromAssetHash, initialAmount); + BindAssetHashEvent(fromAssetHash, toChainId, toAssetHash, initialAmount); + return true; +} +``` + +**Parameters setup:** + +- fromAssetHash: the asset contract hash on the source chain +- toChainId: the target chain ID +- toAssetHash: the asset contract hash on the target chain +- initialAmount: initial locked amount + +This method binds the NEP5 token contract hash with the corresponding asset hash on the target chain, such as the USDT contract hash, and sets the cross chain amount limit. + +### Lock + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (fromAssetHash.Length != 20) + { + Runtime.Notify("The parameter fromAssetHash SHOULD be 20-byte long."); + return false; + } + if (fromAddress.Length != 20) + { + Runtime.Notify("The parameter fromAddress SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + + // get the corresbonding asset on target chain + var toAssetHash = GetAssetHash(fromAssetHash, toChainId); + if (toAssetHash.Length == 0) + { + Runtime.Notify("Target chain asset hash not found."); + return false; + } + + // get the proxy contract on target chain + var toContract = GetProxyHash(toChainId); + if (toContract.Length == 0) + { + Runtime.Notify("Target chain proxy contract not found."); + return false; + } + + // transfer asset from fromAddress to proxy contract address, use dynamic call to call nep5 token's contract "transfer" + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)fromAssetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { fromAddress, currentHash, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to proxy contract."); + return false; + } + + // construct args for proxy contract on target chain + var inputBytes = SerializeArgs(toAssetHash, toAddress, amount); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(fromAssetHash).ToBigInteger(); + lockedAmount.Put(fromAssetHash, old + amount); + + LockEvent(fromAssetHash, toChainId, toAssetHash, fromAddress, toAddress, amount); + + return true; +} +``` + +**Parameters setup:** + +- fromAssetHash: the NEP5 contract hash on the source chain +- fromAddress: the user account address starting the cross chain operation where mining fee is deducted +- toChainId: the target chain ID +- toAddress: the user account address on the target chain +- amount: the cross chain asset amount + +This method locks the NEP5 asset in the contract address, and invokes the `CrossChain` method of the CCMC to start a cross chain operation. + +### Unlock + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromProxyContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedProxy = GetProxyHash(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromProxyContract.AsBigInteger() != storedProxy.AsBigInteger()) + { + Runtime.Notify(fromProxyContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedProxy); + Runtime.Notify("From proxy contract not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var assetHash = (byte[])results[0]; + var toAddress = (byte[])results[1]; + var amount = (BigInteger)results[2]; + if (assetHash.Length != 20) + { + Runtime.Notify("ToChain Asset script hash SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length != 20) + { + Runtime.Notify("ToChain Account address SHOULD be 20-byte long."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // transfer asset from proxy contract to toAddress + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)assetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { currentHash, toAddress, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to toAddress."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(assetHash).ToBigInteger(); + lockedAmount.Put(assetHash, old - amount); + + UnlockEvent(assetHash, toAddress, amount); + + return true; +} +``` + +**Parameters setup:** + +- inputBytes: serialized parameters for business logic +- fromProxyContract: the proxy hash starting the cross chain operation on the source chain +- fromChainId: the source chain ID +- caller: the caller of this method, no need to pass by, the contract adds this parameter automatically + +This method is only called by the CCMC and returns the previously locked NEP5 asset to the user. + +In conclusion, the proxy contract is mainly used to bind, lock and unlock the NEP5 asset. "lock" means users transfer their asset to the proxy contract address and "unlock" does the opposite. Developers can either deploy their own proxy contracts, or use the existing ones. Multiple NEP5 assets can share a proxy contract. diff --git a/neo/README.md b/neo/README.md new file mode 100644 index 0000000..7d505f6 --- /dev/null +++ b/neo/README.md @@ -0,0 +1,61 @@ +# Design of Neo Cross Chain + +English | [中文](README_CN.md) + +## Overview + +Cross chain is the procedure of how information interacts between two or more block chains. The information can be the business logic of an dApp, or an asset released on one chain and etc. Currently there are two ways to achieve cross chain operation on Neo: one is to use full nodes like Neo-CLI and Neo-GUI to invoke contracts and send out cross chain transactions, the other is to construct cross chain transactions with SDK. + +## Cross chain implementation + +Take asset cross chain as an example: users should be able to lock their assets on the source chain and release corresponding assets on the target chain, they also can request to withdraw their assets on the target chain and finally unlock their assets from the source chain. To achieve this goal, it is required that what has happened on the source chain must be verifiable by the target chain, in this case, which means the target chain can verify that the cross chain transactions do exist and certain amount of assets are locked on the source chain. + +It is same for other cross chain information. The target chain needs to verify the information change on the source chain and make corresponding actions. + +Currently such verification procedures are carried out in the form of merkle proof. The source chain stores cross chain information and constrct a merkle tree, and then send the merkle tree root and the merkle proof of the information to the target chain. The target chain verify the proof using the merkle root and confirm the validity of the cross chain information. + +## Cross chain structure + +







