Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thoughts for a generic service #1

Open
juanfranblanco opened this issue Jun 15, 2019 · 5 comments
Open

Thoughts for a generic service #1

juanfranblanco opened this issue Jun 15, 2019 · 5 comments

Comments

@juanfranblanco
Copy link

@Enigmatic331
These are some thoughts for a generic service, with some small refactoring of your implementation.

The main idea is to:

  1. Have a generic interface that all Delegated signed functions have to adhere.
    Parameter attributes cannot be put here (or create a base class) as we don't know how many extra parameters we will have. Another option will be to use a struct matching the message if using Solidity V2 in future versions, which will be far much easier when doing mappings
public interface IDelegatedSignedFunction
    {
        string Owner { get; set; }
        BigInteger Fee { get; set; }
        BigInteger DelegatedNonce { get; set; }
        byte[] Sig { get; set; }
        string FeeAccount { get; set; }
    }

Then a generic service can be put in place as follows.

  public partial class SignedTransferFunction : SignedTransferFunctionBase { }

    [Function("signedTransfer", "bool")]
    public class SignedTransferFunctionBase : FunctionMessage
    {
        [Parameter("address", "tokenOwner", 1)]
        public virtual string Owner { get; set; }
        [Parameter("address", "to", 2)]
        public virtual string To { get; set; }
        [Parameter("uint256", "tokens", 3)]
        public virtual BigInteger Tokens { get; set; }
        [Parameter("uint256", "fee", 4)]
        public virtual BigInteger Fee { get; set; }
        [Parameter("uint256", "nonce", 5)]
        public virtual BigInteger DelegatedNonce { get; set; }
        [Parameter("bytes", "sig", 6)]
        public virtual byte[] Sig { get; set; }
        [Parameter("address", "feeAccount", 7)]
        public virtual string FeeAccount { get; set; }
    }

    //my custom partial implementing IDelegatedSignedFunction
    public partial class SignedTransferFunction : SignedTransferFunctionBase, IDelegatedSignedFunction
    {
       
    }


    public partial class TransferFunction : TransferFunctionBase { }

    [Function("transfer", "bool")]
    public class TransferFunctionBase : FunctionMessage
    {
        [Parameter("address", "_to", 1)]
        public virtual string To { get; set; }
        [Parameter("uint256", "_value", 2)]
        public virtual BigInteger Value { get; set; }
    }



    public interface IDelegatedSignedFunction
    {
        string Owner { get; set; }
        BigInteger Fee { get; set; }
        BigInteger DelegatedNonce { get; set; }
        byte[] Sig { get; set; }
        string FeeAccount { get; set; }
    }


    public class TokenDelegatedSignerService : DelegatedSignerService<TransferFunction, SignedTransferFunction>
    {
        public override SignedTransferFunction MapDelegatedFunctionToSignedFunction(TransferFunction delegatedFunction,
            SignedTransferFunction delegatedSignedFunction)
        {
            delegatedSignedFunction.To = delegatedFunction.To;
            delegatedSignedFunction.Tokens = delegatedFunction.Value;
            return delegatedSignedFunction;
        }

        public override TransferFunction MapSignedFunctionToDelegatedSignedFunction(TransferFunction delegatedFunction,
            SignedTransferFunction delegatedSignedFunction)
        {
            delegatedFunction.To = delegatedSignedFunction.To;
            delegatedFunction.Value = delegatedSignedFunction.Tokens;
            return delegatedFunction;
        }
    }

    public abstract class DelegatedSignerService<TDelegatedFunction, TDelegatedSignedFunction> where TDelegatedFunction: FunctionMessage, new()
        where TDelegatedSignedFunction: FunctionMessage, IDelegatedSignedFunction, new()
    {
        public string ContractAddress { get; set; }

        public string GetDelegatedFunctionSignature()
        {
            return ABITypedRegistry.GetFunctionABI<TDelegatedFunction>().Sha3Signature;
        }

        public string GetHashToSign(TDelegatedFunction delegatedFunction, string owner, BigInteger nonce, BigInteger fee)
        {
            var abiEncode = new ABIEncode();
            return Sha3Keccack.Current.CalculateHashFromHex(
                GetDelegatedFunctionSignature(),  
                abiEncode.GetABIEncodedPacked("address", ContractAddress).ToHex(),
                abiEncode.GetABIEncodedPacked("address", owner).ToHex(),
                delegatedFunction.GetParamsEncodedPacked().ToHex(),
                abiEncode.GetABIEncodedPacked("uint256", nonce).ToHex(),
                abiEncode.GetABIEncodedPacked("uint256", fee).ToHex()
            );
        }

        public byte[] GetSignature(TDelegatedFunction delegatedFunction, BigInteger delegatedNonce, BigInteger fee, string privateKey)
        {
            var ethEcKey = new EthECKey(privateKey);

            var messageSigner = new Nethereum.Signer.EthereumMessageSigner();
            return messageSigner.Sign(GetHashToSign(delegatedFunction, ethEcKey.GetPublicAddress(), delegatedNonce, fee).HexToByteArray(), ethEcKey).HexToByteArray();
        }

        public TDelegatedSignedFunction GetSignedRequest(TDelegatedFunction delegatedFunction, BigInteger delegatedNonce, BigInteger fee, string feeAccount,
            string privateKey)
        {
            var signedFunction = new TDelegatedSignedFunction();
            signedFunction = MapDelegatedFunctionToSignedFunction(delegatedFunction, signedFunction);
            signedFunction.Fee = fee;
            signedFunction.DelegatedNonce = delegatedNonce;
            signedFunction.FeeAccount = feeAccount;
            signedFunction.Sig = GetSignature(delegatedFunction, delegatedNonce, fee, privateKey);
            return signedFunction;
        }

        public TDelegatedSignedFunction GetSignedRequest(TDelegatedSignedFunction delegatedSignedFunctionToBeSigned,
            string privateKey)
        {
            var delegatedFunction =
                MapSignedFunctionToDelegatedSignedFunction(new TDelegatedFunction(), delegatedSignedFunctionToBeSigned);
            delegatedSignedFunctionToBeSigned.Sig = GetSignature(delegatedFunction, delegatedSignedFunctionToBeSigned.DelegatedNonce, delegatedSignedFunctionToBeSigned.Fee, privateKey);
            return delegatedSignedFunctionToBeSigned;
        }

        public abstract TDelegatedSignedFunction MapDelegatedFunctionToSignedFunction(
            TDelegatedFunction delegatedFunction, TDelegatedSignedFunction delegatedSignedFunction);

        public abstract TDelegatedFunction MapSignedFunctionToDelegatedSignedFunction(
            TDelegatedFunction delegatedFunction, TDelegatedSignedFunction delegatedSignedFunction);

    }
@juanfranblanco
Copy link
Author

I have not tested it btw )

@Enigmatic331
Copy link
Owner

Thanks for this @juanfranblanco!!!! Very nice separation of "proxying" and "purpose" (which in this case is token transfer) - I understand better now the details you were explaining to me via DM. :)

I've implemented this and tested it so overall it works fine - Just did some minor adjustments:

  1. GetDelegatedFunctionSignature - The signature we want to get is the signed function's signature, so I passed in TDelegatedSignedFunction.
  2. GetABIEncodedPacked(New ABIValue("address", owner)).ToHex... and likewise for the rest.

Two things I noticed while doing the implementation and would like to know more:

  1. MapDelegatedFunctionToSignedFunction/MapSignedFunctionToDelegatedSignedFunction - I suppose these are overrides to allow for a cleaner segregation between the generic parameters required by proxying, and the extra parameters required for the purpose of the function call? Mostly thinking of a nice way to explain this for the sample, to make sure its usefulness is properly communicated. I myself was tempted to just immediately access the parameters on the SignedTransferFunctionBase while refactoring.

  2. This is something slightly off-topic: Noticed the class Sha3Keccack, think it should be spelt Sha3Keccak - How should typos be approached for on the Nethereum library? In case it breaks any currently in production systems....

A few more evaluation lines to add in and I'll get the refactored version uploaded later in the morning, then continue producing the sample.

Thanks again Juan! Much appreciated. Sorry for taking so long though - Decided to torture myself by hand-converting it to VB. :D

@juanfranblanco
Copy link
Author

Great @Enigmatic331 and thanks to you :)

On the errors:

Did I mention I did not test it ;) ? So I totally agree with GetDelegatedFunctionSignature and AbiEncodedPacked. What a muppet, in both. Hence I love integration testing https://twitter.com/richcampbell/status/1139548144390352897, although unit testing would have helped too on the Abi stuff.

On the MapDelegatedFunctionToSignedFunction/MapSignedFunctionToDelegatedSignedFunction:

The idea of having an abstract class is to allow for any delegated signature, so you could have any "SignedXXXFunction" or "DelegatedXXXFunction" and reuse the logic.

The way I see it working is that users (higher level developers) may only interact with the XXXFunction and have an intermediary service (another one) that can retrieve or have configured, the fees approved, contract address, nonce, etc.

@juanfranblanco
Copy link
Author

Sorry about the Vb.Net I am the other way around.. you can use http://converter.telerik.com/ it is very helpful (I think we have already talked about it in the past). I would love to have an F# version although completely different beast.

@Enigmatic331
Copy link
Owner

Thanks Juan. :)

I've pushed the commit at: 6784289

Could I trouble you to have a look please to see if the implementation makes sense (i.e. if I've used the DelegatedSignerService as intended)?

Sorry about the Vb.Net I am the other way around.. you can use http://converter.telerik.com/ it is very helpful (I think we have already talked about it in the past). I would love to have an F# version although completely different beast.

Oh no please don't apologise. Yea we had the conversation before and Telerik did help me on bits and parts of the snippet, very helpful recommendation. :)

Next up will be updating the readme with implementation notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants