您当前的位置: 首页 > 

MateZero

暂无认证

  • 2浏览

    0关注

    92博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

涉及到动态数组时Solidity与Vyper智能合约相互调用方法

MateZero 发布时间:2021-02-09 15:54:27 ,浏览量:2

涉及到动态数组时Solidity与Vyper智能合约相互调用
    • 前言
    • 合约编写
    • 编译部署
    • 测试脚本

前言

我们知道,当前以太坊的智能合约编程语言主要有两种,一种叫Solidity,大家很熟悉了;一种叫Vyper,虽然是后起之秀,但比较小众,用到的项目很少。Vyper的安全性其实是高于Solidity的,但为什么这么小众呢?个人觉得最主要的原因是不支持动态数组,这样使用它来编写复杂的应用就太麻烦了。

那么问题来了,如果一个智能合约是Solidity编写的而另一个智能合约是Vyper编写的。它们之间要相互调用,但是涉及的接口在Solidity中使用了动态数组作为输入输出参数,在Vyper中使用了固定大小的数组作为输入输出参数。那么他们之间怎么调用呢?

直接调用?会由于参数不匹配而调用失败。那能不能相互调用,怎么调用呢?凡事要打破沙锅问到底,要弄明白。因为不管什么语言写的智能合约,运行的都是字节码,底层调用时都是操作码,因此个人觉得相互之间是可以调用的。但觉得没有用,得亲自实践,必须得研究尝试。经过反复测试,发现它们之间是可以相互调用的,虽然有些限制。

合约编写

为了进行测试,我们准备了三个合约。合约A,使用Solidity编写,提供一个输入输出参数都是动态数组的接口。合约B,使用VYPER编写,提供一个输入输出参数都是固定大小数组的接口。合约C,使用Solidity编写,分别调用A和B的接口进行测试。

使用Solidity编写的合约A和C的源代码如下:

pragma solidity ^0.6.0;

//Vyper 合约B,使用fixed list。
interface IB {
    function callTwice(uint[] calldata source) external pure returns(uint[] memory target);
}

//合约A 使用Solidity 动态数组
contract A {
    function callTwice(uint[] calldata source) external pure returns(uint[] memory target) {
        uint len = source.length;
        target = new uint[](len);
        for(uint i=0;i uint256[3]:
    result: uint256[3] =  [0,0,0]
    for i in range(3):
        result[i] = source[i] * 2
    return result


# 直接调用A的callTwice方法,由于参数不符,调用会失败
@public
@constant
def callTwiceA(source:uint256[3]) -> uint256[3]:
    return  IA(self.a).callTwice(source)


#解析返回的结果
@private
def uintArrayResponse(unitBytes: bytes[160]) -> uint256[3]:
    result: uint256[3] = [0,0,0]
    for i in range(3):
        start: int128 = 32*(2+i)
        extracted: bytes32 = extract32(unitBytes, start, type=bytes32)
        result[i] = convert(extracted, uint256)
    return result


# 通过原生方式调用合约A的callTwice方法,注意此方法无法标记为@constant,但是不影响外部获取结果。
@public
def callARaw(source:uint256[3]) -> uint256[3]:
    funcSig: bytes[4] = method_id("callTwice(uint256[])", bytes[4])
    #offset + lengthOfInputArray + inputArray
    uintBytes: bytes[160] = concat(
                                convert(32, bytes32),
                                convert(3, bytes32),
                                convert(source[0], bytes32),
                                convert(source[1], bytes32),
                                convert(source[2], bytes32)
                            )
    full_data: bytes[164] = concat(funcSig, uintBytes)
     # returns byteArray of offset (32 bytes) + length (32 bytes) + uintArray (32 * 3)
    response: bytes[160] = raw_call(
                                self.a,           # Compound Comptroller address
                                full_data,          # funcSig + offset + lengthOfInputArray + inputArray
                                outsize=160,         # outsize = offset (32 bytes) + length (32 bytes) + addressArray (32 * 1)
                                gas=msg.gas,        # Pass msg.gas for call
                                value=0,            # Make sure to not send ETH
                                delegate_call=False # Not delegate_call
                                # static_call=True
                              )
    return self.uintArrayResponse(response)

合约代码都不是很难,也加上了注释,这里就不再讲解了。

编译部署

我们可以使用truffle + ganache来在本地编译部署智能合约并进行测试。truffle 和 ganache的基本使用相信大家都会了,这里简单介绍一下truffle编译vyper智能合约的方法:

  1. 安装python 3.6以上
  2. 使用pip安装指定版本的vyper编译器,例如 `pip install vyper==0.1.0b16
  3. truffle compile

这里具体编译和部署的过程就跳过去了,比较简单的。

测试脚本

既然涉及到Vyper,而Vyper又使用了Python语法,我们就使用python来测试好了。

前提: 安装web3.py

测试脚本如下:

from contract import A,B,C
from web3.auto import w3

source = [1,2,3]


#计算函数选择器,参数示例:func = 'callTwice(uint235[3])'
def calSelectorByPython(_func):
    result = w3.keccak(text=_func)
    selector = (w3.toHex(result))[:10]
    return selector


def testNormal():
    r1 = A.functions.callTwice(source).call() #solidity
    r2 = B.functions.callTwice(source).call() #vyper
    print(r1) # 输出[2,4,6]
    print(r2) # 输出[2,4,6]


def testC():
    r1 = C.functions.callTwiceA(source).call() #Solidity => Solidity
    print(r1) # 输出[2,4,6]
    r2 = C.functions.callTwiceB(source).call() #Solidity => vyper
    print(r2)  #会失败
   
def testCRaw():
    r = C.functions.callTwiceBRaw(source).call() #Solidity => vyper(raw)
    print(r)  # 输出[2,4,6]

def testBToA():
    r = B.functions.callTwiceA(source).call() # vyper => solidity
    print(r) #会失败


def testBToARaw():
    r = B.functions.callARaw(source).call() # vyper => solidity (raw)
    print(r) # 输出[2,4,6]

这里A,B,C是三个合约的实例,具体怎么生成的我这里不再列出了,有兴趣的读者可以看一下web3.py相关内容。 这里只是简单讲一下它的测试接口:

  1. calSelectorByPython 计算函数选择器,合约调用时是根据函数选择器来匹配对应的函数的。
  2. testNormal 分别使用外部账号正常调用合约A和C的接口,输出正确结果
  3. testC分为两个操作, 操作1在C中直接调用A合约的接口,注释中提到了是Solidity之间相互调用 ,输出正确结果。操作2在C中直接调用合约B的接口,注释中提到了是Solidity调用Vyper,由于动态数组的问题,这里的参数不会匹配,调用失败。
  4. testCRaw 在C中使用原生(底层)方法来调用合约B的接口,这里绕过了一些检查,因此调用成功,输出正确。
  5. testBToA 同testC第二步相反,我们在B中直接调用合约A的相关接口,这里同样由于动态数组的问题,参数不匹配,调用失败。
  6. testBToARaw 我们在B中通过底层方法调用合约A的相关接口,这里绕过了一些检查,因此调用成功,输出正确。

从上面五个测试中,我们可以得出:涉及到动态数组时,Vyper与Solidity智能合约之间相互直接调用会失败,必须通过底层调用才能成功。

但是底层调用一是复杂费力,二是每个函数都要写单独的匹配方法,无法复用。

结论:当涉及到动态数组时,请使用Solidity,请使用Solidity,请使用Solidity,重要的事情说三遍!

由于这个原因,Vyper仅适合小众的项目,或者不需要动态数组的项目。

关注
打赏
1648304347
查看更多评论
立即登录/注册

微信扫码登录

0.0404s