Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Generate ABI together with AVM #34

Open
igormcoelho opened this issue Jan 26, 2018 · 10 comments
Open

Generate ABI together with AVM #34

igormcoelho opened this issue Jan 26, 2018 · 10 comments
Labels
HACKIT Hackable Issue for Hackathons

Comments

@igormcoelho
Copy link

igormcoelho commented Jan 26, 2018

Hi! I strongly appreciate neo-boa project, and I just wonder if you plan to generate an Application Binary Interface (ABI) json description of the compiled functions (parameters/return types) together with the .avm. This is an interesting feature from NEO C# compiler.
Best regards!

@localhuman
Copy link
Collaborator

I think its a good idea! Probably related to this new functionality here: #31

@localhuman localhuman added the HACKIT Hackable Issue for Hackathons label Mar 16, 2018
@saltyskip
Copy link

ScriptHash (big endian): 0x746d6cc63dacd7b275bb3a3a06d54859661591a6
Entry Point:Main
Functions:
	String Name();
	String Symbol();
	Integer Decimals();
	ByteArray Main(String operation, Array args);
	Boolean Deploy();
	Boolean MintTokens();
	Integer TotalSupply();
	Boolean Transfer(ByteArray from, ByteArray to, Integer value);
	Integer BalanceOf(ByteArray address);
Events:
	Void transfer(ByteArray arg1, ByteArray arg2, Integer arg3);
	Void refund(ByteArray arg1, Integer arg2);

Here is an example of ABI output from NEO C# compiler for an ICO template

@ixje CityOfZion/neo-python#908

@igormcoelho
Copy link
Author

It would be nice to have some advances here... although I don't think it's easy to do :)

@ixje
Copy link
Member

ixje commented Mar 8, 2019

I'm guessing the biggest hurdle is going to be supporting type hints. @saltyskip can you also link the C# template that matches this ABI output?

@saltyskip
Copy link

using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using Neo.SmartContract.Framework.Services.System;
using System;
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract
{
    public class ICO_Template : Framework.SmartContract
    {
        //Token Settings
        public static string Name() => "name of the token";
        public static string Symbol() => "SymbolOfTheToken";
        public static readonly byte[] Owner = "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y".ToScriptHash();
        public static byte Decimals() => 8;
        private const ulong factor = 100000000; //decided by Decimals()
        private const ulong neo_decimals = 100000000;

        //ICO Settings
        private static readonly byte[] neo_asset_id = { 155, 124, 255, 218, 166, 116, 190, 174, 15, 147, 14, 190, 96, 133, 175, 144, 147, 229, 254, 86, 179, 74, 92, 34, 12, 205, 207, 110, 252, 51, 111, 197 };
        private const ulong total_amount = 100000000 * factor; // total token amount
        private const ulong pre_ico_cap = 30000000 * factor; // pre ico token amount
        private const ulong basic_rate = 1000 * factor;
        private const int ico_start_time = 1506787200;
        private const int ico_end_time = 1538323200;

        [DisplayName("transfer")]
        public static event Action<byte[], byte[], BigInteger> Transferred;

        [DisplayName("refund")]
        public static event Action<byte[], BigInteger> Refund;

        public static Object Main(string operation, params object[] args)
        {
            if (Runtime.Trigger == TriggerType.Verification)
            {
                if (Owner.Length == 20)
                {
                    // if param Owner is script hash
                    return Runtime.CheckWitness(Owner);
                }
                else if (Owner.Length == 33)
                {
                    // if param Owner is public key
                    byte[] signature = operation.AsByteArray();
                    return VerifySignature(signature, Owner);
                }
            }
            else if (Runtime.Trigger == TriggerType.Application)
            {
                if (operation == "deploy") return Deploy();
                if (operation == "mintTokens") return MintTokens();
                if (operation == "totalSupply") return TotalSupply();
                if (operation == "name") return Name();
                if (operation == "symbol") return Symbol();
                if (operation == "transfer")
                {
                    if (args.Length != 3) return false;
                    byte[] from = (byte[])args[0];
                    byte[] to = (byte[])args[1];
                    BigInteger value = (BigInteger)args[2];
                    return Transfer(from, to, value);
                }
                if (operation == "balanceOf")
                {
                    if (args.Length != 1) return 0;
                    byte[] account = (byte[])args[0];
                    return BalanceOf(account);
                }
                if (operation == "decimals") return Decimals();
            }
            //you can choice refund or not refund
            byte[] sender = GetSender();
            ulong contribute_value = GetContributeValue();
            if (contribute_value > 0 && sender.Length != 0)
            {
                Refund(sender, contribute_value);
            }
            return false;
        }

        // initialization parameters, only once
        // 初始化参数
        public static bool Deploy()
        {
            byte[] total_supply = Storage.Get(Storage.CurrentContext, "totalSupply");
            if (total_supply.Length != 0) return false;
            Storage.Put(Storage.CurrentContext, Owner, pre_ico_cap);
            Storage.Put(Storage.CurrentContext, "totalSupply", pre_ico_cap);
            Transferred(null, Owner, pre_ico_cap);
            return true;
        }

        // The function MintTokens is only usable by the chosen wallet
        // contract to mint a number of tokens proportional to the
        // amount of neo sent to the wallet contract. The function
        // can only be called during the tokenswap period
        // 将众筹的neo转化为等价的ico代币
        public static bool MintTokens()
        {
            byte[] sender = GetSender();
            // contribute asset is not neo
            if (sender.Length == 0)
            {
                return false;
            }
            ulong contribute_value = GetContributeValue();
            // the current exchange rate between ico tokens and neo during the token swap period
            // 获取众筹期间ico token和neo间的转化率
            ulong swap_rate = CurrentSwapRate();
            // crowdfunding failure
            // 众筹失败
            if (swap_rate == 0)
            {
                Refund(sender, contribute_value);
                return false;
            }
            // you can get current swap token amount
            ulong token = CurrentSwapToken(sender, contribute_value, swap_rate);
            if (token == 0)
            {
                return false;
            }
            // crowdfunding success
            // 众筹成功
            BigInteger balance = Storage.Get(Storage.CurrentContext, sender).AsBigInteger();
            Storage.Put(Storage.CurrentContext, sender, token + balance);
            BigInteger totalSupply = Storage.Get(Storage.CurrentContext, "totalSupply").AsBigInteger();
            Storage.Put(Storage.CurrentContext, "totalSupply", token + totalSupply);
            Transferred(null, sender, token);
            return true;
        }

        // get the total token supply
        // 获取已发行token总量
        public static BigInteger TotalSupply()
        {
            return Storage.Get(Storage.CurrentContext, "totalSupply").AsBigInteger();
        }

        // function that is always called when someone wants to transfer tokens.
        // 流转token调用
        public static bool Transfer(byte[] from, byte[] to, BigInteger value)
        {
            if (value <= 0) return false;
            if (!Runtime.CheckWitness(from)) return false;
            if (to.Length != 20) return false;

            BigInteger from_value = Storage.Get(Storage.CurrentContext, from).AsBigInteger();
            if (from_value < value) return false;
            if (from == to) return true;
            if (from_value == value)
                Storage.Delete(Storage.CurrentContext, from);
            else
                Storage.Put(Storage.CurrentContext, from, from_value - value);
            BigInteger to_value = Storage.Get(Storage.CurrentContext, to).AsBigInteger();
            Storage.Put(Storage.CurrentContext, to, to_value + value);
            Transferred(from, to, value);
            return true;
        }

        // get the account balance of another account with address
        // 根据地址获取token的余额
        public static BigInteger BalanceOf(byte[] address)
        {
            return Storage.Get(Storage.CurrentContext, address).AsBigInteger();
        }

        // The function CurrentSwapRate() returns the current exchange rate
        // between ico tokens and neo during the token swap period
        private static ulong CurrentSwapRate()
        {
            const int ico_duration = ico_end_time - ico_start_time;
            uint now = Runtime.Time;
            int time = (int)now - ico_start_time;
            if (time < 0)
            {
                return 0;
            }
            else if (time < ico_duration)
            {
                return basic_rate;
            }
            else
            {
                return 0;
            }
        }

        //whether over contribute capacity, you can get the token amount
        private static ulong CurrentSwapToken(byte[] sender, ulong value, ulong swap_rate)
        {
            ulong token = value / neo_decimals * swap_rate;
            BigInteger total_supply = Storage.Get(Storage.CurrentContext, "totalSupply").AsBigInteger();
            BigInteger balance_token = total_amount - total_supply;
            if (balance_token <= 0)
            {
                Refund(sender, value);
                return 0;
            }
            else if (balance_token < token)
            {
                Refund(sender, (token - balance_token) / swap_rate * neo_decimals);
                token = (ulong)balance_token;
            }
            return token;
        }

        // check whether asset is neo and get sender script hash
        private static byte[] GetSender()
        {
            Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;
            TransactionOutput[] reference = tx.GetReferences();
            // you can choice refund or not refund
            foreach (TransactionOutput output in reference)
            {
                if (output.AssetId == neo_asset_id) return output.ScriptHash;
            }
            return new byte[]{};
        }

        // get smart contract script hash
        private static byte[] GetReceiver()
        {
            return ExecutionEngine.ExecutingScriptHash;
        }

        // get all you contribute neo amount
        private static ulong GetContributeValue()
        {
            Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;
            TransactionOutput[] outputs = tx.GetOutputs();
            ulong value = 0;
            // get the total amount of Neo
            // 获取转入智能合约地址的Neo总量
            foreach (TransactionOutput output in outputs)
            {
                if (output.ScriptHash == GetReceiver() && output.AssetId == neo_asset_id)
                {
                    value += (ulong)output.Value;
                }
            }
            return value;
        }
    }
}

@saltyskip
Copy link

Apologies for the direct code post, It is the template on neocompilereco, and I could not find where it is actually hosted 😅

@ixje
Copy link
Member

ixje commented Mar 8, 2019

Is it me or is the ABI output missing an entry for

 public static readonly byte[] Owner = "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y".ToScriptHash();

as it looks to output all public accessors.

Perhaps I'm a bit skeptical, but is this ABI output not kind of useless without knowing a mapping of "operation name => function"? e.g. the operation "deploy" maps to Boolean Deploy();.

It also seems to force you into using a fixed Main signature and implementation structure of

public static Object Main(string operation, params object[] args)
   if (operation == 'name') return Name()

which is severely limited due to the 16 objects max array length of args. Any thoughts?
Or are there perhaps plans to move to an "Ethereum model" where you can call the public functions directly? Then the ABI actually makes sense.

@ixje
Copy link
Member

ixje commented Mar 8, 2019

I just noticed that the sample ABI doesn't follow NEP3

sample ABI:

Events:
	Void transfer(ByteArray arg1, ByteArray arg2, Integer arg3);

NEP3 suggestion

{
	"hash": "0x746d6cc63dacd7b275bb3a3a06d54859661591a6",
	"entrypoint": "main",
	"functions": [],
	"events": [
		{
			"name": "refund",
			"parameters": [
				{
					"name": "arg1"
					"type": "ByteArray"
				},
				{
					"name": "arg2"
					"type": "ByteArray"
				},
						{
					"name": "arg3"
					"type": "Integer"
				},
			]
		}
	]
}

I believe we need more alignment before this can be considered.

@saltyskip
Copy link

I think that is correct because it only outputs functions not publicly accessible static variables. Although that would be a nice addition.

@igormcoelho do you do any custom formatting on neocompiler eco?

@hal0x2328
Copy link
Member

hal0x2328 commented Mar 29, 2019

@igormcoelho do you do any custom formatting on neocompiler eco?

From my testing it looks like NeoCompiler Eco takes the ABI output from the neon compiler with all the function names, then assumes that the operation name will be the same name (only in camelCase) when it comes time to choose one to invoke.

Of course, there's no guideline for function vs operation naming in NEP-3 so a lot of contract authors are going to name their operations one thing and their corresponding functions another, and their generated ABI will be fairly useless to NeoCompiler Eco or other tools that parse the ABI.

For neo-boa, I like the idea of using Pythonic type hints and building the ABI based on any function or event definition that contains hints, using the name of the function verbatim as the operation name.

Then, we need to amend NEP-3 with guidance to require any operation that is meant to be included in the ABI have a function name with its exact match.

Given the top-down-from-Main invocation pattern built in to the Neo VM I don't see a perfect way to solve the problem without requiring some diligence on the part of contract authors, but at least this way it will at least be easy enough for contract authors to implement after some light reading.

Edit: I see Erik had replied in the NEP-3 PR to the issue of function names and case with the following:

C# methods starts with upper case.

While you can certainly write a valid C# function name that starts with lower-case, it is technically part of the C# convention to use PascalCase for everything except parameter names. So I doubt he'll want to amend NEP-3 to suggest making a C# contract use bool transferFrom( instead of bool TransferFrom

However the C# compiler could be modified to automatically switch the ABI function names to camelCase instead of relying on the parser to do so, at least this way the different compilers could be made to be consistent in their ABI output if nothing else.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
HACKIT Hackable Issue for Hackathons
Projects
None yet
Development

No branches or pull requests

5 participants