长安链Solidity智能合约调用原理分析

你的名字 2024-05-08 07:12 135阅读 0赞

目录

ERC20智能合约案例

JAVA-SDK调用案例

调用分析

JAVA-SDK生成地址

合约部署

合约调用

msg.sender


ERC20智能合约案例

  1. pragma solidity ^0.4.11;
  2. contract Token {
  3. string public name = "token"; // token name
  4. string public symbol = "TK"; // token symbol
  5. uint256 public decimals = 6; // token digit
  6. mapping (address => uint256) public balanceOf;
  7. mapping (address => mapping (address => uint256)) public allowance;
  8. uint256 public totalSupply = 0;
  9. bool public stopped = false;
  10. uint256 constant valueFounder = 100000000000000000;
  11. address owner = 0x0;
  12. modifier isOwner {
  13. assert(owner == msg.sender);
  14. _;
  15. }
  16. modifier isRunning {
  17. assert (!stopped);
  18. _;
  19. }
  20. modifier validAddress {
  21. assert(0x0 != msg.sender);
  22. _;
  23. }
  24. constructor (address _addressFounder) public {
  25. owner = msg.sender;
  26. totalSupply = valueFounder;
  27. balanceOf[_addressFounder] = valueFounder;
  28. emit Transfer(0x0, _addressFounder, valueFounder);
  29. }
  30. function transfer(address _to, uint256 _value) public isRunning validAddress returns (bool success) {
  31. require(balanceOf[msg.sender] >= _value);
  32. require(balanceOf[_to] + _value >= balanceOf[_to]);
  33. balanceOf[msg.sender] -= _value;
  34. balanceOf[_to] += _value;
  35. emit Transfer(msg.sender, _to, _value);
  36. return true;
  37. }
  38. function transferFrom(address _from, address _to, uint256 _value) public isRunning validAddress returns (bool success) {
  39. require(balanceOf[_from] >= _value);
  40. require(balanceOf[_to] + _value >= balanceOf[_to]);
  41. require(allowance[_from][msg.sender] >= _value);
  42. balanceOf[_to] += _value;
  43. balanceOf[_from] -= _value;
  44. allowance[_from][msg.sender] -= _value;
  45. emit Transfer(_from, _to, _value);
  46. return true;
  47. }
  48. function approve(address _spender, uint256 _value) public isRunning validAddress returns (bool success) {
  49. require(_value == 0 || allowance[msg.sender][_spender] == 0);
  50. allowance[msg.sender][_spender] = _value;
  51. emit Approval(msg.sender, _spender, _value);
  52. return true;
  53. }
  54. function stop() public isOwner {
  55. stopped = true;
  56. }
  57. function start() public isOwner {
  58. stopped = false;
  59. }
  60. function setName(string _name) public isOwner {
  61. name = _name;
  62. }
  63. function burn(uint256 _value) public {
  64. require(balanceOf[msg.sender] >= _value);
  65. balanceOf[msg.sender] -= _value;
  66. balanceOf[0x0] += _value;
  67. emit Transfer(msg.sender, 0x0, _value);
  68. }
  69. event Transfer(address indexed _from, address indexed _to, uint256 _value);
  70. event Approval(address indexed _owner, address indexed _spender, uint256 _value);
  71. }

JAVA-SDK调用案例

  1. public class TestEvmContract extends TestBase {
  2. private static final String EVM_CONTRACT_FILE_PATH = "token.bin";
  3. private static final String CONTRACT_NAME = "token";
  4. private static final String CONTRACT_ARGS_EVM_PARAM = "data";
  5. private static String ADDRESS = "";
  6. private void makeAddrFromCert() {
  7. try {
  8. ADDRESS = CryptoUtils.makeAddrFromCert(chainClient.getClientUser().getCertificate());
  9. } catch (UtilsException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. @Test
  14. public void testCreateEvmContract() {
  15. //address根据用户证书生成
  16. makeAddrFromCert();
  17. //创建合约构造参数扽RLP编码值
  18. Function function = new Function( "" , Arrays.asList(new Address(ADDRESS)),
  19. Collections.emptyList());
  20. String methodDataStr = FunctionEncoder.encode(function);
  21. Map<String, String> paramMap = new HashMap<>();
  22. paramMap.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr.substring(10));
  23. ResponseInfo responseInfo = null;
  24. try {
  25. byte[] byteCode = FileUtils.getResourceFileBytes(EVM_CONTRACT_FILE_PATH);
  26. // 1. create payload
  27. byte[] payload = chainClient.createPayloadOfContractCreation(CONTRACT_NAME,
  28. "1", ContractOuterClass.RuntimeType.EVM, paramMap, Hex.decode(new String(byteCode)));
  29. // 2. create payloads with endorsement
  30. byte[] payloadWithEndorsement1 = adminUser1.signPayloadOfContractMgmt(payload, chainClient.isEnabledCertHash());
  31. // 3. merge endorsements using payloadsWithEndorsement
  32. byte[][] payloadsWithEndorsement = new byte[1][];
  33. payloadsWithEndorsement[0] = payloadWithEndorsement1;
  34. byte[] payloadWithEndorsement = chainClient.mergeSignedPayloadsOfContractMgmt(payloadsWithEndorsement);
  35. // 4. create contract
  36. responseInfo = chainClient.createContract(payloadWithEndorsement, 10000, 10000);
  37. } catch (SdkException e) {
  38. e.printStackTrace();
  39. Assert.fail(e.getMessage());
  40. }
  41. Assert.assertNotNull(responseInfo);
  42. }
  43. @Test
  44. public void testInvokeTransferEvmContract() {
  45. Map<String, String> params = new HashMap<>();
  46. String add = "0x684a7b71376f6a28af5447516d2448dbe7d84460";
  47. String toAddress ="0x06ffc656405700dd26aa1b2f2f38f55935f69661";
  48. BigInteger amount = BigInteger.valueOf(600);
  49. Function function = new Function( "transfer" , Arrays.asList(new Address(toAddress), new Uint256(amount)),
  50. Collections.emptyList());
  51. String methodDataStr = FunctionEncoder.encode(function);
  52. String method = methodDataStr.substring(0,10);
  53. params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr);
  54. ResponseInfo responseInfo = null;
  55. try {
  56. responseInfo = chainClient.invokeContract(CONTRACT_NAME, method, params, 10000, 10000);
  57. } catch (SdkException e) {
  58. e.printStackTrace();
  59. Assert.fail(e.getMessage());
  60. }
  61. Assert.assertNotNull(Numeric.toBigInt(responseInfo.getSyncResultTxInfo().getTransaction().getResult().getContractResult().getResult().toByteArray()));
  62. Assert.assertNotNull(responseInfo);
  63. }
  64. @Test
  65. public void testInvokeBalanceOfEvmContract() {
  66. Map<String, String> params = new HashMap<>();
  67. makeAddrFromCert();
  68. Function function = new Function( "balanceOf" , Arrays.asList(new Address(ADDRESS)),
  69. Collections.emptyList());
  70. String methodDataStr = FunctionEncoder.encode(function);
  71. String method = methodDataStr.substring(0,10);
  72. params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr);
  73. ResponseInfo responseInfo = null;
  74. try {
  75. responseInfo = chainClient.invokeContract(CONTRACT_NAME, method, params, 10000, 10000);
  76. } catch (SdkException e) {
  77. e.printStackTrace();
  78. Assert.fail(e.getMessage());
  79. }
  80. Assert.assertNotNull(Numeric.toBigInt(responseInfo.getSyncResultTxInfo().getTransaction().getResult().getContractResult().getResult().toByteArray()));
  81. Assert.assertNotNull(responseInfo);
  82. }
  83. }

调用分析

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

2、调用合约部署的方法中会遇到两个web3j的类。

  1. Function function = new Function( "" , Arrays.asList(new Address(ADDRESS)),
  2. Collections.emptyList());
  3. String methodDataStr = FunctionEncoder.encode(function);

Function里边主要构造的是合约方法名、合约请求参数(以集合形式传递)、合约响应参数(以集合形式传递)。

token.sol合约中的构造器方法需要一个初始化账户地址,并会在合约部署后给这个地址

  1. 100000000000000000的金额,故第二个参数即为此初始化账户。

FunctionEncoder当然就是编译abi码了。

3、部署合约与其他类型合约处理方法相同。

合约调用

合约调用时主要注意如下几个细节。

1、调用合约的参数

  1. Function function = new Function("transfer", Arrays.asList(new Address(toAddress), new Uint256(amount)),
  2. Collections.emptyList());

“transfer”为token.sol里边的转账方法名,第二个集合中所传递的toAddress和amount即为转账方法的两个参数,注意类型需要完全匹配。

2、合约方法名和合约名称的取值

相对于其他类型的合约可以直接传递合约方法的字符串,Solidity智能合约需要传编码后的值,这个是所有Solidity智能合约的通用规则。详见

Contract ABI Specification

  1. String method = methodDataStr.substring(0,10);

这里的10是一个固定值。

  1. Utils.calcContractName(CONTRACT_NAME)

3、params参数

  1. params.put(CONTRACT_ARGS_EVM_PARAM, methodDataStr.getBytes());

相对于其他合约params里边传的都是自定义合约的参数,但是在长安链中针对Solidity智能合约这里是一个“data”的常量(不可变更)。如果你没见过evm合约的调用示例,估计自己琢磨,这里将是你一个难以逾越的鸿沟(O(∩_∩)O)。要想找到这个data的出处还得去源码中找。长安链的evm有专门的开源项目。

chainmaker / vm-evm · ChainMaker

这个“data”的出处就在runtime.go的invoke方法里边

  1. func (r *RuntimeInstance) Invoke(contract *commonPb.Contract, method string, byteCode []byte,
  2. parameters map[string][]byte, txSimContext protocol.TxSimContext, gasUsed uint64) (
  3. contractResult *commonPb.ContractResult, specialTxType protocol.ExecOrderTxType)
  4. params := string(parameters[protocol.ContractEvmParamKey])

这个protocol.ContractEvmParamKey的背后藏的就是data。

msg.sender

了解完上边的好像差不多,合约也部署了,也能正常调用了。但是长安链还有一个比较坑的点就是这个msg.sender。这里处理不好很容易导致合约调用出现

  1. message: "OK"
  2. contract_result {
  3. code: 1
  4. message: "revert instruction was encountered during execution"
  5. }
  6. tx_id: "89ee744e75ad9066f339aa4f49942e09a0692f30e48e1e03257adb2cce26839e"

1、首先通过SDK源码找到sender的到底是谁

源码位置在JAVA SDK的ChainClient.java中,详细如下:

  1. private TxRequest createTxRequest(Payload payload, EndorsementEntry[] endorsementEntries) throws ChainMakerCryptoSuiteException {
  2. Member sender;
  3. org.chainmaker.pb.common.Request.EndorsementEntry.Builder endorsementEntryBuilder;
  4. if (this.clientUser.getAuthType().equals(AuthType.PermissionedWithCert.getMsg())) {
  5. if (this.isEnabledCertHash && this.clientUser.getCertHash() != null && this.clientUser.getCertHash().length > 0) {
  6. sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getCertHash())).setMemberType(MemberType.CERT_HASH).build();
  7. } else if (this.isEnabledAlias && this.clientUser.getAlias() != null && this.clientUser.getAlias().length() > 0) {
  8. sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getAlias().getBytes())).setMemberType(MemberType.ALIAS).build();
  9. } else {
  10. sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getCertBytes())).setMemberType(MemberType.CERT).build();
  11. }
  12. endorsementEntryBuilder = EndorsementEntry.newBuilder().setSigner(sender).setSignature(ByteString.copyFrom(this.clientUser.getCryptoSuite().sign(this.clientUser.getPrivateKey(), payload.toByteArray())));
  13. } else {
  14. sender = Member.newBuilder().setOrgId(this.clientUser.getOrgId()).setMemberInfo(ByteString.copyFrom(this.clientUser.getPukBytes())).setMemberType(MemberType.PUBLIC_KEY).build();
  15. endorsementEntryBuilder = EndorsementEntry.newBuilder().setSigner(sender).setSignature(ByteString.copyFrom(this.clientUser.getCryptoSuite().rsaSign(this.clientUser.getPrivateKey(), payload.toByteArray())));
  16. }
  17. org.chainmaker.pb.common.Request.TxRequest.Builder txRequestBuilder = TxRequest.newBuilder().setPayload(payload).setSender(endorsementEntryBuilder);
  18. if (endorsementEntries != null) {
  19. txRequestBuilder.addAllEndorsers(Arrays.asList((Object[])endorsementEntries.clone()));
  20. }
  21. return txRequestBuilder.build();
  22. }

2、里边有个this.clientUser他又是谁

是我们节点的client用户吗,这里很容易让人产生误解,答案是不一定。这里的clientUser实际就是代表ChainClient里边的一个用户,这个用户怎么创建的,又得网上找谁创建的ChainClient,此时就找到ChainManager.java,里边有个createChainClient方法,如下:

  1. public synchronized ChainClient createChainClient(SdkConfig sdkConfig) throws ChainClientException, RpcServiceClientException, UtilsException, ChainMakerCryptoSuiteException {
  2. this.checkConfig(sdkConfig.getChainClient());
  3. String chainId = sdkConfig.getChainClient().getChainId();
  4. ChainClientConfig chainClientConfig = sdkConfig.getChainClient();
  5. this.dealChainClientConfig(chainClientConfig);
  6. User clientUser;
  7. if (!chainClientConfig.getAuthType().equals(AuthType.PermissionedWithKey.getMsg()) && !chainClientConfig.getAuthType().equals(AuthType.Public.getMsg())) {
  8. clientUser = new User(sdkConfig.getChainClient().getOrgId(), chainClientConfig.getUserSignKeyBytes(), chainClientConfig.getUserSignCrtBytes(), chainClientConfig.getUserKeyBytes(), chainClientConfig.getUserCrtBytes());
  9. if (sdkConfig.getChainClient().getAlias() != null && sdkConfig.getChainClient().getAlias().length() > 0) {
  10. clientUser.setAlias(sdkConfig.getChainClient().getAlias());
  11. }
  12. } else {
  13. clientUser = new User(sdkConfig.getChainClient().getOrgId());
  14. clientUser.setPukBytes(CryptoUtils.getPemStrFromPublicKey(chainClientConfig.getPublicKey()).getBytes());
  15. clientUser.setPublicKey(chainClientConfig.getPublicKey());
  16. clientUser.setPrivateKey(CryptoUtils.getPrivateKeyFromBytes(FileUtils.getFileBytes(chainClientConfig.getUserSignKeyFilePath())));
  17. }
  18. 。。。。未截全。。。。

到这里相当于知道了是哪个用户,但是一个用户有几个证书,此时还得再看看User的构造方法和前边sender的创建时取的哪一个证书,最终确认为用户的签名证书,也就是sdk_config.yaml配置的

  1. user_sign_crt_file_path

只有梳理清楚后,后续在调用比如transferFrom、approve方法时才不容易报错。

发表评论

表情:
评论列表 (有 0 条评论,135人围观)

还没有评论,来说两句吧...

相关阅读