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); } }
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()); } }
// 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()); }