public void Test_SetMinter()
        {
            var tx = new TransactionReceipt();

            var context  = new InvocationContext(_mintCntrlAdd, _stateManager.LastApprovedSnapshot, tx);
            var contract = new NativeTokenContract(context);

            // set the minter
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodSetMinter, _minterAdd);
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.SetMinter(_minterAdd, frame));
                var decoder = new ContractDecoder(frame.ReturnValue);
                var res     = decoder.Decode("uint160")[0] as UInt160 ?? throw new Exception("Invalid return value format");
                Assert.AreEqual(_minterAdd, res);
            }

            // get the minter
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodGetMinter);
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.GetMinter(frame));
                var decoder = new ContractDecoder(frame.ReturnValue);
                var res     = decoder.Decode("uint160")[0] as UInt160 ?? throw new Exception("Invalid return value format");
                Assert.AreEqual(_minterAdd, res);
            }
        }
Exemple #2
0
        public void Test_SendRawTransactionContractInvocation()
        {
            var rawTx2 = "0xf8848001832e1a3094010000000000000000000000000000000000000080a4c76d99bd000000000000000000000000000000000000000000042300c0d3ae6a03a0000075a0f5e9683653d203dc22397b6c9e1e39adf8f6f5ad68c593ba0bb6c35c9cd4dbb8a0247a8b0618930c5c4abe178cbafb69c6d3ed62cfa6fa33f5c8c8147d096b0aa0";
            var ethTx  = new TransactionChainId(rawTx2.HexToBytes());

            var t = _apiService !.MakeTransaction(ethTx);

            var r = ethTx.Signature.R;

            while (r.Length < 32)
            {
                r = "00".HexToBytes().Concat(r).ToArray();
            }

            var s = ethTx.Signature.S;

            while (s.Length < 32)
            {
                s = "00".HexToBytes().Concat(s).ToArray();
            }

            var signature = r.Concat(s).Concat(ethTx.Signature.V).ToArray();

            var keyPair = new EcdsaKeyPair("0xE83385AF76B2B1997326B567461FB73DD9C27EAB9E1E86D26779F4650C5F2B75"
                                           .HexToBytes().ToPrivateKey());
            var receipt = _transactionSigner.Sign(t, keyPair, true);

            Assert.AreEqual(receipt.Signature, signature.ToSignature(true));

            var ethTx2 = t.GetEthTx(receipt.Signature, true);

            Assert.AreEqual(ethTx.ChainId, ethTx2.ChainId);
            Assert.AreEqual(ethTx.Data, ethTx2.Data);
//            Assert.AreEqual(ethTx.Nonce,  ethTx2.Nonce);
            Assert.AreEqual(ethTx.Signature.R, ethTx2.Signature.R);
            Assert.AreEqual(ethTx.Signature.S, ethTx2.Signature.S);
            Assert.AreEqual(ethTx.Signature.V, ethTx2.Signature.V);
//            Assert.AreEqual(ethTx.Value,  ethTx2.Value);
            Assert.AreEqual(ethTx.GasLimit, ethTx2.GasLimit);
            Assert.AreEqual(ethTx.GasPrice, ethTx2.GasPrice);
            Assert.AreEqual(ethTx.ReceiveAddress, ethTx2.ReceiveAddress);
            //Assert.AreEqual(ethTx,  ethTx2);

            var txid = _apiService !.SendRawTransaction(rawTx2);

            // check we get a transaction hash,  not error message
            Assert.AreEqual("0x", txid.Substring(0, 2));
            // check this hash is not empty
            Assert.AreNotEqual("0x", txid);

            // check encoding is correct
            var decoder = new ContractDecoder(t.Invocation.ToByteArray());
            var args    = decoder.Decode(Lrc20Interface.MethodSetAllowedSupply);
            var res     = args[0] as UInt256 ?? throw new Exception("Failed to decode invocation");
            var supply  = new BigInteger(5001000) * BigInteger.Pow(10, 18);;

            Console.WriteLine($"supply: {supply}");
            Assert.AreEqual(res.ToHex(), supply.ToUInt256().ToHex());
        }
        public void Test_MaxSupply()
        {
            var tx = new TransactionReceipt();

            var context  = new InvocationContext(_mintCntrlAdd, _stateManager.LastApprovedSnapshot, tx);
            var contract = new NativeTokenContract(context);

            var keyPair = new EcdsaKeyPair("0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32"
                                           .HexToBytes().ToPrivateKey());
            var address = keyPair.PublicKey.GetAddress();

            // set the allowedSupply
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodSetAllowedSupply, Money.Parse("10000"));
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.SetAllowedSupply(Money.Parse("10000").ToUInt256(), frame));
            }

            // set the allowedSupply > maxLimit
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodSetAllowedSupply, Money.Parse("1000000001"));
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.SetAllowedSupply(Money.Parse("1000000001").ToUInt256(), frame));
            }

            // verify allowedSupply
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodGetAllowedSupply);
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.GetAllowedSupply(frame));
                var decoder = new ContractDecoder(frame.ReturnValue);
                var res     = decoder.Decode("uint256")[0] as UInt256 ?? throw new Exception("Invalid return value format");
                Assert.AreEqual(Money.Parse("10000"), res.ToMoney());
            }

            // mint tokens to address
            {
                context  = new InvocationContext(_stateManager.LastApprovedSnapshot.Balances.GetMinter(), _stateManager.LastApprovedSnapshot, tx);
                contract = new NativeTokenContract(context);

                var input = ContractEncoder.Encode(Lrc20Interface.MethodMint, address, Money.Parse("1000000000"));
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.Mint(address, Money.Parse("1000000000").ToUInt256(), frame));
            }
        }
        public void Test_InvalidMintController()
        {
            var tx       = new TransactionReceipt();
            var context  = new InvocationContext(_mintCntrlAdd, _stateManager.LastApprovedSnapshot, tx);
            var contract = new NativeTokenContract(context);

            // set minter
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodSetMinter, _minterAdd);
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.SetMinter(_minterAdd, frame));
                var decoder = new ContractDecoder(frame.ReturnValue);
                var res     = decoder.Decode("uint160")[0] as UInt160 ?? throw new Exception("Invalid return value format");
                Assert.AreEqual(_minterAdd, res);
            }


            // set the allowedSupply
            {
                context  = new InvocationContext(_stateManager.LastApprovedSnapshot.Balances.GetMinter(), _stateManager.LastApprovedSnapshot, tx);
                contract = new NativeTokenContract(context);

                var input = ContractEncoder.Encode(Lrc20Interface.MethodSetAllowedSupply, Money.Parse("10000"));
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.SetAllowedSupply(Money.Parse("10000").ToUInt256(), frame));
            }

            // verify allowedSupply
            {
                var input = ContractEncoder.Encode(Lrc20Interface.MethodGetAllowedSupply);
                var call  = _contractRegisterer.DecodeContract(context, ContractRegisterer.NativeTokenContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.GetAllowedSupply(frame));
                var decoder = new ContractDecoder(frame.ReturnValue);
                var res     = decoder.Decode("uint256")[0] as UInt256 ?? throw new Exception("Invalid return value format");
                Assert.AreEqual(Money.Parse("0"), res.ToMoney());
            }
        }
Exemple #5
0
        // For every cycle, a new set of keys are required for the validators. This key generation process
        // is done on-chain. That means, every communication between participating nodes happen via transactions
        // in the block. For example, if node A wants to send a msg to node B, then node A encrypts the
        // msg with node B's public key and broadcast this as a transaction to the governance contract.
        // After this transaction is added to the chain, node B can decrypt the msg and read it.

        // During block execution, after every system transaction is executed, the following method
        // is invoked. It evaluates the transaction and if it's keygen related, it produces
        // appropriate response in form of a transaction and adds it to the pool for the addition
        // in the block.

        private void BlockManagerOnSystemContractInvoked(object _, InvocationContext context)
        {
            if (context.Receipt is null)
            {
                return;
            }
            var highestBlock    = _blockSynchronizer.GetHighestBlock();
            var willParticipate =
                !highestBlock.HasValue ||
                GovernanceContract.IsKeygenBlock(context.Receipt.Block) &&
                GovernanceContract.SameCycle(highestBlock.Value, context.Receipt.Block);

            if (!willParticipate)
            {
                Logger.LogInformation(
                    highestBlock != null
                        ? $"Will not participate in keygen: highest block is {highestBlock.Value}, call block is {context.Receipt.Block}"
                        : $"Will not participate in keygen: highest block is null, call block is {context.Receipt.Block}"
                    );
            }

            var tx = context.Receipt.Transaction;

            if (
                !tx.To.Equals(ContractRegisterer.GovernanceContract) &&
                !tx.To.Equals(ContractRegisterer.StakingContract)
                )
            {
                return;
            }
            if (context.Receipt.Block < _blockManager.GetHeight() &&
                !GovernanceContract.SameCycle(context.Receipt.Block, _blockManager.GetHeight()))
            {
                Logger.LogWarning(
                    $"System contract invoked from outdated tx: {context.Receipt.Hash}, tx block {context.Receipt.Block}, our height is {_blockManager.GetHeight()}");
                return;
            }

            if (tx.Invocation.Length < 4)
            {
                return;
            }

            var signature       = ContractEncoder.MethodSignatureAsInt(tx.Invocation);
            var decoder         = new ContractDecoder(tx.Invocation.ToArray());
            var contractAddress = tx.To;

            if (contractAddress.Equals(ContractRegisterer.GovernanceContract) && signature ==
                ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodFinishCycle))
            {
                Logger.LogDebug("Aborting ongoing keygen because cycle was finished");
                _keyGenRepository.SaveKeyGenState(Array.Empty <byte>());
            }
            else if (signature == ContractEncoder.MethodSignatureAsInt(StakingInterface.MethodFinishVrfLottery))
            {
                Logger.LogDebug($"Detected call of StakingInterface.{StakingInterface.MethodFinishVrfLottery}");
                var cycle      = GovernanceContract.GetCycleByBlockNumber(context.Receipt.Block);
                var data       = new GovernanceContract(context).GetNextValidators();
                var publicKeys =
                    (data ?? throw new ArgumentException("Cannot parse method args"))
                    .Select(x => x.ToPublicKey())
                    .ToArray();
                Logger.LogDebug(
                    $"Keygen is started in cycle={cycle}, block={context.Receipt.Block} for validator set: {string.Join(",", publicKeys.Select(x => x.ToHex()))}"
                    );
                if (!publicKeys.Contains(_privateWallet.EcdsaKeyPair.PublicKey))
                {
                    Logger.LogWarning("Skipping validator change event since we are not new validator");
                    return;
                }

                var keygen = GetCurrentKeyGen();
                if (keygen != null && keygen.Cycle == cycle)
                {
                    throw new ArgumentException("Cannot start keygen, since one is already running");
                }

                if (keygen != null)
                {
                    Logger.LogWarning($"Aborted keygen for cycle {keygen.Cycle} to start keygen for cycle {cycle}");
                }

                _keyGenRepository.SaveKeyGenState(Array.Empty <byte>());

                var faulty = (publicKeys.Length - 1) / 3;
                keygen = new TrustlessKeygen(_privateWallet.EcdsaKeyPair, publicKeys, faulty, cycle);
                var commitTx = MakeCommitTransaction(keygen.StartKeygen(), cycle);
                Logger.LogTrace($"Produced commit tx with hash: {commitTx.Hash.ToHex()}");
                if (willParticipate)
                {
                    Logger.LogInformation($"Try to send KeyGen Commit transaction");
                    if (_transactionPool.Add(commitTx) is var error && error != OperatingError.Ok)
                    {
                        Logger.LogError($"Error creating commit transaction ({commitTx.Hash.ToHex()}): {error}");
                    }
                    else
                    {
                        Logger.LogInformation($"KeyGen Commit transaction sent");
                    }
                }

                Logger.LogDebug($"Saving keygen {keygen.ToBytes().ToHex()}");
                _keyGenRepository.SaveKeyGenState(keygen.ToBytes());
            }