NEO改进协议提案4(NEP-4)

144 阅读5分钟

文章目录

  • 摘要
  • 动机
  • 详述
    • neo
    • neo-vm
    • 智能合约示例
  • 原理
  • 向后兼容性
  • 实现

摘要

此NEP提案概述了一种机制,通过该机制,智能合约能够调用直到运行时才知道的其他智能合约,而不仅限于调用在编译时定义的智能合约。为了保持智能合约与未来动态分片过程接口的能力,包括一份用于构建智能合约的提案详述会表示智能合约是否需要动态合约调用功能。

动机

此NEP的动机是为让智能合约(SC)的的作者能够为编译时不可知的SC提供接口。例如,操作NEP-5 token的分散交换的SC可以调用在运行时才确定的token的SC的transferFrom方法。目前,这样的SC需要对所有支持的NEP-5 token地址进行硬编码,并在添加新token时重新发布。以太坊上的许多SC都需要此功能,包括任何符合ERC223 token标准的功能。通过让SC作者能够在运行时指定SC与之交互,类似于更先进的以太坊合约中包含的功能将更容易开发和维护。
值得注意的是,向NEO添加动态SC调用会影响可伸缩性。通过动态SC调用,我们不用再事先知道将调用哪些其他SC,因此VM状态的子集必须可用于执行才能成功。这使得动态分片更难实现。
为了克服可扩展性的缺点,该提议在区块链上创建时为每个SC添加一个设定,以表示它是否需要动态调用功能。该设定将允许所有现存的合约和大多数未来合约在预先已知的存储上下文中执行,因此更适合于动态分片,同时还使SC更强大和更具表现力。
考虑到动态调用功能的可扩展性缺陷,该NEP建议了一个需要此功能的SC的更新费用结构。下面列出了更新费用结构的样例实现。

详述

该提案概述了Neo项目的三个部分的变更,并提供了如何在SC中使用此变更的示例:
•neo
•neo-vm
•neo编译器
•智能合约实例
下面列出的变更并非试图详尽无遗,而是概述每个库中所需的重要变更。

neo

为了让一份SC表示其是否能够动态调用其他SC,该NEP建议在neo.Core.ContractState对象中添加以下属性,且默认值为false
public bool HasDynamicInvoke
HasDynamicInvoke属性
为了使实现与当前的Neo协议保持互操作,HasDynamicInvoke属性将被序列化为字节标志跟在现有的HasStorage属性之后:

    [Flags]
    public enum ContractPropertyState : byte
    {
        NoProperty = 0,
        HasStorage = 1 << 0,
        HasDynamicInvoke = 1 << 1,
    }
public class ContractState : StateBase, ICloneable<ContractState>
    {
...
public ContractPropertyState ContractProperties;
public bool HasStorage => ContractProperties.HasFlag(ContractPropertyState.HasStorage)
public bool HasDynamicInvoke => ContractProperties.HasFlag(ContractPropertyState.HasDynamicInvoke)
…

public override void Serialize(BinaryWriter writer)
        {
            base.Serialize(writer);
            writer.WriteVarBytes(Script);
            writer.WriteVarBytes(ParameterList.Cast<byte>().ToArray());
            writer.Write((byte)ReturnType);
            writer.Write(ContractProperties);   // currently is writer.Write(HasStorage)
            writer.WriteVarString(Name);
            writer.WriteVarString(CodeVersion);
            writer.WriteVarString(Author);
            writer.WriteVarString(Email);
            writer.WriteVarString(Description);
        }

以下在neo.SmartContract.ApplicationEngine中的变更用于计量合约创建不同的Gas费用。没有额外附加功能的合约创建费用会被降低,而那些HasDynamicInvoke或HasStorage属性为true的合约创建会产生额外的费用。
        protected virtual long GetPriceForSysCall()
        {
           // lines omitted
           ...
case "Neo.Contract.Create":
                case "Neo.Contract.Migrate":
                case "AntShares.Contract.Create":
                case "AntShares.Contract.Migrate":
                    
                    long fee = 100L;
                    ContractState contract = PeekContractState() // this would need to be implemented
                    
                    if( contract.HasStorage ) 
                    {
                      fee += 400L
                    }
                
                    if( contract.HasDynamicInvoke ) 
                    {
                      fee += 500L;
                    }
                
                    return fee * 100000000L / ratio;
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253

neo-vm

本详细提案给NEO虚拟机添加了一个新OpCode,代表相对于静态的动态AppCall的使用
DYNAMICCALL = 0xFA
DYNAMICCALL OpCode 在neo.VM.ExecutionEngine.ExecuteOp 方法的执行也按照以下方式不同于现有的APPCALL和TAILCALL OpCodes

                case OpCode.APPCALL:
                case OpCode.TAILCALL:
                case OpCode.DYNAMICCALL:
                    {
                        if (table == null)
                        {
                            State |= VMState.FAULT;
                            return;
                        }
byte[] script_hash = null;
if ( opcode == OpCode.DYNAMICCALL ) 
                        {
script_hash = EvaluationStack.Pop().GetByteArray();
                            
                            if ( script_hash.Length != 20 ) 
                            {
                              State |= VMState.FAULT
                              return;
                            }
} else {
script_hash = context.OpReader.ReadBytes(20);
                        }
byte[] script = table.GetScript(script_hash);
                        if (script == null)
                        {
                            State |= VMState.FAULT;
                            return;
                        }
                        if (opcode == OpCode.TAILCALL || opcode == OpCode.DYNAMICCALL)
                            InvocationStack.Pop().Dispose();
                        LoadScript(script);
                    }
                    break;
123456789101112131415161718192021222324252627282930313233

neo编译器
将方法调用转换为DYNAMICCALL的示例方法如下:

else if (calltype == CallType.DYNAMICCALL)
            {
                _ConvertPush(callhash, null, to)
                _Convert1by1(VM.OpCode.DYNAMICCALL, null, to);
}
12345

智能合约示例

以下是一个SC演示了所提案功能的简单使用

using Neo.SmartContract.Framework.Services.Neo;
namespace Neo.SmartContract
{
    public class DynamicTotalSupply : Framework.SmartContract
    {
        public static int Main(byte[] contract_hash)
        {
        
            if( contract_hash.Length == 20 ) {
            
                BigInteger totalSupply = DynamicCall( contract_hash, 'totalSupply')
            
                return totalSupply;
            }
            
            return 0;
        }
    }
}
12345678910111213141516171819

原理

用动态SC调用来动态分片(以太坊已经提出了许多解决方案)并不是不可能的,尽管这会增添已经很困难的任务。
仅仅因为我们事先知道计算调用图并不意味着我们能够成功地完美分配资源,没有重叠的子集。仍然可能需要实现分片之间的通信,如以太坊提案中那样。
考虑到这一点,不向SC添加任何关于是否需要动态调用的元数据并实现动态应用程序调用是有可能的,因为它们可以以相同的方式执行和延申。
但是,即使在可以实现一个系统可以动态SC调用和动态分片这种情况下,本提案仍认为存储HasDynamicInvoke属性在该实现中可能是很有用的。
存储此属性还能使系统对使用HasDynamicInvoke属性发布的SC收取不同的费用。

向后兼容性

本NEP介绍了一套不影响现有SC的新功能。
通过利用现有字节来指示SC是否需要存储区或添加额外标记,我们能够在不影响现有网络协议的情况下维持现有功能和添加该新功能。

实现

• neo-project/neo: github.com/neo-project…
• neo-project/neo-vm: github.com/neo-project…

原文链接: github.com/neo-project…