长安链Solidity智能合约调用原理分析
目录
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中所支持的地址格式。主要流程为:
- 通过证书获取其中的SKI信息
- 将SKI进行HASH(Keccak256)并截取,生成address
合约部署
1、合约部署需要先见token.sol编译成token.bin文件,编译方法详见官网:
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
这个“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方法时才不容易报错。
还没有评论,来说两句吧...