长安链Solidity智能合约调用原理分析 你的名字 2024-05-08 07:12 19阅读 0赞 **目录** ERC20智能合约案例 JAVA-SDK调用案例 调用分析 JAVA-SDK生成地址 合约部署 合约调用 msg.sender -------------------- ## ERC20智能合约案例 ## pragma solidity ^0.4.11; contract Token { string public name = "token"; // token name string public symbol = "TK"; // token symbol uint256 public decimals = 6; // token digit mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; uint256 public totalSupply = 0; bool public stopped = false; uint256 constant valueFounder = 100000000000000000; address owner = 0x0; modifier isOwner { assert(owner == msg.sender); _; } modifier isRunning { assert (!stopped); _; } modifier validAddress { assert(0x0 != msg.sender); _; } constructor (address _addressFounder) public { owner = msg.sender; totalSupply = valueFounder; balanceOf[_addressFounder] = valueFounder; emit Transfer(0x0, _addressFounder, valueFounder); } function transfer(address _to, uint256 _value) public isRunning validAddress returns (bool success) { require(balanceOf[msg.sender] >= _value); require(balanceOf[_to] + _value >= balanceOf[_to]); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; emit Transfer(msg.sender, _to, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public isRunning validAddress returns (bool success) { require(balanceOf[_from] >= _value); require(balanceOf[_to] + _value >= balanceOf[_to]); require(allowance[_from][msg.sender] >= _value); balanceOf[_to] += _value; balanceOf[_from] -= _value; allowance[_from][msg.sender] -= _value; emit Transfer(_from, _to, _value); return true; } function approve(address _spender, uint256 _value) public isRunning validAddress returns (bool success) { require(_value == 0 || allowance[msg.sender][_spender] == 0); allowance[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } function stop() public isOwner { stopped = true; } function start() public isOwner { stopped = false; } function setName(string _name) public isOwner { name = _name; } function burn(uint256 _value) public { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; balanceOf[0x0] += _value; emit Transfer(msg.sender, 0x0, _value); } event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); } ## JAVA-SDK调用案例 ## public class TestEvmContract extends TestBase { private static final String EVM_CONTRACT_FILE_PATH = "token.bin"; private static final String CONTRACT_NAME = "token"; private static final String CONTRACT_ARGS_EVM_PARAM = "data"; private static String ADDRESS = ""; private void makeAddrFromCert() { try { ADDRESS = CryptoUtils.makeAddrFromCert(chainClient.getClientUser().getCertificate()); } catch (UtilsException e) { e.printStackTrace(); } } @Test public void testCreateEvmContract() { //address根据用户证书生成 makeAddrFromCert(); //创建合约构造参数扽RLP编码值 Function function = new Function( "" , Arrays.asList(new Address(ADDRESS)), Collections.emptyList()); String methodDataStr = FunctionEncoder.encode(function); Map<String, String> paramMap = new HashMap<>(); paramMap.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr.substring(10)); ResponseInfo responseInfo = null; try { byte[] byteCode = FileUtils.getResourceFileBytes(EVM_CONTRACT_FILE_PATH); // 1. create payload byte[] payload = chainClient.createPayloadOfContractCreation(CONTRACT_NAME, "1", ContractOuterClass.RuntimeType.EVM, paramMap, Hex.decode(new String(byteCode))); // 2. create payloads with endorsement byte[] payloadWithEndorsement1 = adminUser1.signPayloadOfContractMgmt(payload, chainClient.isEnabledCertHash()); // 3. merge endorsements using payloadsWithEndorsement byte[][] payloadsWithEndorsement = new byte[1][]; payloadsWithEndorsement[0] = payloadWithEndorsement1; byte[] payloadWithEndorsement = chainClient.mergeSignedPayloadsOfContractMgmt(payloadsWithEndorsement); // 4. create contract responseInfo = chainClient.createContract(payloadWithEndorsement, 10000, 10000); } catch (SdkException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } Assert.assertNotNull(responseInfo); } @Test public void testInvokeTransferEvmContract() { Map<String, String> params = new HashMap<>(); String add = "0x684a7b71376f6a28af5447516d2448dbe7d84460"; String toAddress ="0x06ffc656405700dd26aa1b2f2f38f55935f69661"; BigInteger amount = BigInteger.valueOf(600); Function function = new Function( "transfer" , Arrays.asList(new Address(toAddress), new Uint256(amount)), Collections.emptyList()); String methodDataStr = FunctionEncoder.encode(function); String method = methodDataStr.substring(0,10); params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr); ResponseInfo responseInfo = null; try { responseInfo = chainClient.invokeContract(CONTRACT_NAME, method, params, 10000, 10000); } catch (SdkException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } Assert.assertNotNull(Numeric.toBigInt(responseInfo.getSyncResultTxInfo().getTransaction().getResult().getContractResult().getResult().toByteArray())); Assert.assertNotNull(responseInfo); } @Test public void testInvokeBalanceOfEvmContract() { Map<String, String> params = new HashMap<>(); makeAddrFromCert(); Function function = new Function( "balanceOf" , Arrays.asList(new Address(ADDRESS)), Collections.emptyList()); String methodDataStr = FunctionEncoder.encode(function); String method = methodDataStr.substring(0,10); params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr); ResponseInfo responseInfo = null; try { responseInfo = chainClient.invokeContract(CONTRACT_NAME, method, params, 10000, 10000); } catch (SdkException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } Assert.assertNotNull(Numeric.toBigInt(responseInfo.getSyncResultTxInfo().getTransaction().getResult().getContractResult().getResult().toByteArray())); Assert.assertNotNull(responseInfo); } } ## 调用分析 ## ### JAVA-SDK生成地址 ### ChainMaker目前已支持的证书模型与以太坊的公钥模型不相同,为此ChainMaker在SDK中支持通过证书的SKI字段转换为EVM中所支持的地址格式。主要流程为: 1. 通过证书获取其中的SKI信息 2. 将SKI进行HASH(Keccak256)并截取,生成address ### 合约部署 ### 1、合约部署需要先见token.sol编译成token.bin文件,编译方法详见官网: [5. 智能合约开发 — chainmaker-docs v2.2.1 documentation][5. _ _ chainmaker-docs v2.2.1 documentation] 2、调用合约部署的方法中会遇到两个web3j的类。 Function function = new Function( "" , Arrays.asList(new Address(ADDRESS)), Collections.emptyList()); String methodDataStr = FunctionEncoder.encode(function); Function里边主要构造的是合约方法名、合约请求参数(以集合形式传递)、合约响应参数(以集合形式传递)。 token.sol合约中的构造器方法需要一个初始化账户地址,并会在合约部署后给这个地址 100000000000000000的金额,故第二个参数即为此初始化账户。 FunctionEncoder当然就是编译abi码了。 3、部署合约与其他类型合约处理方法相同。 ### 合约调用 ### 合约调用时主要注意如下几个细节。 1、调用合约的参数 Function function = new Function("transfer", Arrays.asList(new Address(toAddress), new Uint256(amount)), Collections.emptyList()); “transfer”为token.sol里边的转账方法名,第二个集合中所传递的toAddress和amount即为转账方法的两个参数,注意类型需要完全匹配。 2、合约方法名和合约名称的取值 相对于其他类型的合约可以直接传递合约方法的字符串,Solidity智能合约需要传编码后的值,这个是所有Solidity智能合约的通用规则。详见 [Contract ABI Specification][] String method = methodDataStr.substring(0,10); 这里的10是一个固定值。 Utils.calcContractName(CONTRACT_NAME) 3、params参数 params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr.getBytes()); 相对于其他合约params里边传的都是自定义合约的参数,但是在长安链中针对Solidity智能合约这里是一个“data”的常量(不可变更)。如果你没见过evm合约的调用示例,估计自己琢磨,这里将是你一个难以逾越的鸿沟(O(∩\_∩)O)。要想找到这个data的出处还得去源码中找。长安链的evm有专门的开源项目。 [chainmaker / vm-evm · ChainMaker][chainmaker _ vm-evm _ ChainMaker] 这个“data”的出处就在runtime.go的invoke方法里边 func (r *RuntimeInstance) Invoke(contract *commonPb.Contract, method string, byteCode []byte, parameters map[string][]byte, txSimContext protocol.TxSimContext, gasUsed uint64) ( contractResult *commonPb.ContractResult, specialTxType protocol.ExecOrderTxType) params := string(parameters[protocol.ContractEvmParamKey]) 这个protocol.ContractEvmParamKey的背后藏的就是data。 ### msg.sender ### 了解完上边的好像差不多,合约也部署了,也能正常调用了。但是长安链还有一个比较坑的点就是这个msg.sender。这里处理不好很容易导致合约调用出现 message: "OK" contract_result { code: 1 message: "revert instruction was encountered during execution" } tx_id: "89ee744e75ad9066f339aa4f49942e09a0692f30e48e1e03257adb2cce26839e" 1、首先通过SDK源码找到sender的到底是谁 源码位置在JAVA SDK的ChainClient.java中,详细如下: private TxRequest createTxRequest(Payload payload, EndorsementEntry[] endorsementEntries) throws ChainMakerCryptoSuiteException { Member sender; org.chainmaker.pb.common.Request.EndorsementEntry.Builder endorsementEntryBuilder; if (this.clientUser.getAuthType().equals(AuthType.PermissionedWithCert.getMsg())) { if (this.isEnabledCertHash && this.clientUser.getCertHash() != null && this.clientUser.getCertHash().length > 0) { sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getCertHash())).setMemberType(MemberType.CERT_HASH).build(); } else if (this.isEnabledAlias && this.clientUser.getAlias() != null && this.clientUser.getAlias().length() > 0) { sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getAlias().getBytes())).setMemberType(MemberType.ALIAS).build(); } else { sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getCertBytes())).setMemberType(MemberType.CERT).build(); } endorsementEntryBuilder = EndorsementEntry.newBuilder().setSigner(sender).setSignature(ByteString.copyFrom(this.clientUser.getCryptoSuite().sign(this.clientUser.getPrivateKey(), payload.toByteArray()))); } else { sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getPukBytes())).setMemberType(MemberType.PUBLIC_KEY).build(); endorsementEntryBuilder = EndorsementEntry.newBuilder().setSigner(sender).setSignature(ByteString.copyFrom(this.clientUser.getCryptoSuite().rsaSign(this.clientUser.getPrivateKey(), payload.toByteArray()))); } org.chainmaker.pb.common.Request.TxRequest.Builder txRequestBuilder = TxRequest.newBuilder().setPayload(payload).setSender(endorsementEntryBuilder); if (endorsementEntries != null) { txRequestBuilder.addAllEndorsers(Arrays.asList((Object[])endorsementEntries.clone())); } return txRequestBuilder.build(); } 2、里边有个this.clientUser他又是谁 是我们节点的client用户吗,这里很容易让人产生误解,答案是不一定。这里的clientUser实际就是代表ChainClient里边的一个用户,这个用户怎么创建的,又得网上找谁创建的ChainClient,此时就找到ChainManager.java,里边有个createChainClient方法,如下: public synchronized ChainClient createChainClient(SdkConfig sdkConfig) throws ChainClientException, RpcServiceClientException, UtilsException, ChainMakerCryptoSuiteException { this.checkConfig(sdkConfig.getChainClient()); String chainId = sdkConfig.getChainClient().getChainId(); ChainClientConfig chainClientConfig = sdkConfig.getChainClient(); this.dealChainClientConfig(chainClientConfig); User clientUser; if (!chainClientConfig.getAuthType().equals(AuthType.PermissionedWithKey.getMsg()) && !chainClientConfig.getAuthType().equals(AuthType.Public.getMsg())) { clientUser = new User(sdkConfig.getChainClient().getOrgId(), chainClientConfig.getUserSignKeyBytes(), chainClientConfig.getUserSignCrtBytes(), chainClientConfig.getUserKeyBytes(), chainClientConfig.getUserCrtBytes()); if (sdkConfig.getChainClient().getAlias() != null && sdkConfig.getChainClient().getAlias().length() > 0) { clientUser.setAlias(sdkConfig.getChainClient().getAlias()); } } else { clientUser = new User(sdkConfig.getChainClient().getOrgId()); clientUser.setPukBytes(CryptoUtils.getPemStrFromPublicKey(chainClientConfig.getPublicKey()).getBytes()); clientUser.setPublicKey(chainClientConfig.getPublicKey()); clientUser.setPrivateKey(CryptoUtils.getPrivateKeyFromBytes(FileUtils.getFileBytes(chainClientConfig.getUserSignKeyFilePath()))); } 。。。。未截全。。。。 到这里相当于知道了是哪个用户,但是一个用户有几个证书,此时还得再看看User的构造方法和前边sender的创建时取的哪一个证书,最终确认为用户的签名证书,也就是sdk\_config.yaml配置的 user_sign_crt_file_path 只有梳理清楚后,后续在调用比如transferFrom、approve方法时才不容易报错。 [5. _ _ chainmaker-docs v2.2.1 documentation]: https://docs.chainmaker.org.cn/operation/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6.html#solidity [Contract ABI Specification]: https://docs.soliditylang.org/en/latest/abi-spec.html [chainmaker _ vm-evm _ ChainMaker]: https://git.chainmaker.org.cn/chainmaker/vm-evm.git
还没有评论,来说两句吧...