{"content":{"title":"Golang 没有abi文件构造合约请求","body":"写代码的时候有时候只需要调用某一个 合约的方法,但是又不想通过abigen 生成golang的wapper类,这种情况下可以直接代码构造 合约调用,来完成.不依赖完整的 [abi](https:\/\/learnblockchain.cn\/2018\/08\/09\/understand-abi) 文件.当然直接构造 abi字符串然后`abi.JSON()`.只要理解了 `ABI`的创建步骤,使用json文件创建还是代码直接创建都是可以.\r\n\r\n## 观察abigen生成的wapper.go是如何处理合约调用的\r\n\r\n```golang\r\n\/\/ Pancakev2 is an auto generated Go binding around an Ethereum contract.\r\ntype Pancakev2 struct {\r\n\tPancakev2Caller     \/\/ Read-only binding to the contract\r\n\tPancakev2Transactor \/\/ Write-only binding to the contract\r\n\tPancakev2Filterer   \/\/ Log filterer for contract events\r\n}\r\n\r\n\/\/ Pancakev2Caller is an auto generated read-only Go binding around an Ethereum contract.\r\ntype Pancakev2Caller struct {\r\n\tcontract *bind.BoundContract \/\/ Generic contract wrapper for the low level calls\r\n}\r\n\r\n\/\/ Pancakev2Transactor is an auto generated write-only Go binding around an Ethereum contract.\r\ntype Pancakev2Transactor struct {\r\n\tcontract *bind.BoundContract \/\/ Generic contract wrapper for the low level calls\r\n}\r\n\r\n\/\/ Pancakev2Filterer is an auto generated log filtering Go binding around an Ethereum contract events.\r\ntype Pancakev2Filterer struct {\r\n\tcontract *bind.BoundContract \/\/ Generic contract wrapper for the low level calls\r\n}\r\n\r\n```\r\n使用pancakerouter的abi文件生成`.go`包装类 当中定义了结构类.Pancakev2,当中包含三个成员分别是\r\n|名称|作用|\r\n|---|---|\r\n|Pancakev2Caller|绑定合约的只读操作|\r\n|Pancakev2Transactor|绑定合约的写操作|\r\n|Pancakev2Filterer|订阅合约的事件|\r\n在声明定义的时候 都包含一个`*bind.BoundContract`该类并非由abigen 生成的包装类提供.接着但是看名字大概就知道它是对合约操作的封装抽象.实际的类结构如下\r\n```golang\r\n\/\/ BoundContract is the base wrapper object that reflects a contract on the\r\n\/\/ Ethereum network. It contains a collection of methods that are used by the\r\n\/\/ higher level contract bindings to operate.\r\ntype BoundContract struct {\r\n\taddress    common.Address     \/\/ Deployment address of the contract on the Ethereum blockchain\r\n\tabi        abi.ABI            \/\/ Reflect based ABI to access the correct Ethereum methods\r\n\tcaller     ContractCaller     \/\/ Read interface to interact with the blockchain\r\n\ttransactor ContractTransactor \/\/ Write interface to interact with the blockchain\r\n\tfilterer   ContractFilterer   \/\/ Event filtering to interact with the blockchain\r\n}\r\n```\r\n在BoundContract中定义了一个Call方法,它的详细定义如下\r\n```golang\r\n\/\/ Call invokes the (constant) contract method with params as input values and\r\n\/\/ sets the output to result. The result type might be a single field for simple\r\n\/\/ returns, a slice of interfaces for anonymous returns and a struct for named\r\n\/\/ returns.\r\nfunc (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error {\r\n```\r\n看描述就能知道 是合约的只读操作的实际执行类其中的代码分为两步\r\n1.RLP inputdata\r\n2.根据 opts中pending是否为true 确定调用`PendingCallContract`还是`CallContract`后者比前者多了一个blocknumber的参数,实际上是`PendingCallContract`在调用eth_call rpc的时候写死了blocknumber为`pending`\r\n3.利用abi.Unpack来解析返回的数据\r\n\r\n### 构造\r\n依据上面的大致流程,我们可以自行构造这些步骤,再将对应的数据传递给ethclient,此处以`PancakeV2Router.WETH()`为例子来构造调用,该操作相当于合约的ready-only操作,至于write和deploy后面更新.\r\n首先来看下直接使用生成的abi.go是如何调用的\r\n```golang \r\n        client, _ := ethclient.Dial(config.RPCURL)\r\n\taddress := common.HexToAddress(\"0x10ed43c718714eb63d5aa57b78b54704e256024e\")\r\n```\r\n#### 使用abi.go调用WETH\r\n```golang\r\n\tpancakev2, _ := NewPancakev2(address, client)\r\n\tweth, err := pancakev2.WETH(&bind.CallOpts{Pending: true})\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tprintln(weth.String())\r\n```\r\n#### 自己构造调用WETH\r\n```golang\r\n        var inp = *new([]abi.Argument)\r\n\taddrType, err := abi.NewType(\"address\", \"address\", nil)\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\toup := []abi.Argument{\r\n\t\tabi.Argument{\r\n\t\t\tName:    \"address\",\r\n\t\t\tType:    addrType,\r\n\t\t\tIndexed: false,\r\n\t\t},\r\n\t}\r\n\tnewMethod := abi.NewMethod(\"WETH\", \"WETH\", abi.Function, \"pure\", false, false, inp, oup)\r\n\tpack, err := newMethod.Inputs.Pack()\r\n\tvar data = append(newMethod.ID, pack...)\r\n\tctx := context.Background()\r\n\tmsg := ethereum.CallMsg{Data: data, Gas: 1e6, GasPrice: big.NewInt(5 * params.GWei), To: &address, Value: big.NewInt(0)}\r\n\tcontract, err := client.PendingCallContract(ctx, msg\r\n\tif err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tunpack, err := newMethod.Outputs.Unpack(contract)\r\n\tif err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tout0 := *abi.ConvertType(unpack[0], new(common.Address)).(*common.Address)\r\n\r\n\tprintln(out0.String())\r\n```\r\n\r\n构造一个合约方法`newMethod := abi.NewMethod(\"WETH\", \"WETH\", abi.Function, \"pure\", false, false, inp, oup)`.参数含义分别 如下`name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments`在计算method.ID的时候使用的是rawName.inputs,outputs分别指的输入的参数以及输出的参数.他们实际的类型都是`[]abi.Argument`.构造Argument倒是满方便的直接把对应的类型抄一遍就好了,这里有个技巧就是可以对着abi.json文件看一下具体填写规则.特别是对于[]tupe 这种.暂时就想到这些,又想起来的再补充"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/2578","address":"0xed8B514066B6900660d0588D9fcf0536770Ab4fD"},"history":"QmeqC4mnjEJUP6GBBN9x8PW3sPmVvwcyHR2SQ4atXzRpZ4","timestamp":1665218251,"version":1}