public void DepositTokens(Address from, string symbol, BigInteger amount) { Runtime.Expect(IsWitness(from), "invalid witness"); var info = Runtime.Nexus.GetTokenInfo(symbol); Runtime.Expect(info.IsFungible, "must be fungible"); var unitAmount = UnitConversion.GetUnitValue(info.Decimals); Runtime.Expect(amount >= unitAmount, "invalid amount"); _total += amount; var balance = GetAvailable(symbol); balance += amount; _balances.Set <string, BigInteger>(symbol, balance); Runtime.Expect(Runtime.Nexus.TransferTokens(Runtime, symbol, from, Runtime.Chain.Address, amount), "tokens transfer failed"); Runtime.Notify(EventKind.TokenSend, from, new TokenEventData() { chainAddress = Runtime.Chain.Address, symbol = symbol, value = amount }); }
public void RegisterScript(Address target, byte[] script) { Runtime.Expect(target.IsUser, "must be user address"); Runtime.Expect(target != Runtime.GenesisAddress, "address must not be genesis"); Runtime.Expect(Runtime.IsWitness(target), "invalid witness"); var stake = Runtime.GetStake(target); Runtime.Expect(stake >= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals), "must have something staked"); Runtime.Expect(script.Length < 1024, "invalid script length"); Runtime.Expect(!_scriptMap.ContainsKey(target), "address already has a script"); var witnessCheck = Runtime.InvokeTrigger(script, AccountTrigger.OnWitness.ToString(), target); Runtime.Expect(witnessCheck, "script does not handle OnWitness correctly, case #1"); witnessCheck = Runtime.InvokeTrigger(script, AccountTrigger.OnWitness.ToString(), Address.Null); Runtime.Expect(!witnessCheck, "script does not handle OnWitness correctly, case #2"); _scriptMap.Set(target, script); // TODO? Runtime.Notify(EventKind.AddressRegister, target, script); }
public void SingleUploadSuccessMaxFileSize() { var owner = KeyPair.Generate(); var simulator = new ChainSimulator(owner, 1234); var nexus = simulator.Nexus; var testUser = KeyPair.Generate(); BigInteger accountBalance = (Archive.MaxSize / 1024) / KilobytesPerStake; //provide enough account balance for max file size available space accountBalance *= UnitConversion.GetUnitValue(Nexus.StakingTokenDecimals); Transaction tx = null; simulator.BeginBlock(); simulator.GenerateTransfer(owner, testUser.Address, nexus.RootChain, Nexus.FuelTokenSymbol, 100000000); simulator.GenerateTransfer(owner, testUser.Address, nexus.RootChain, Nexus.StakingTokenSymbol, accountBalance); simulator.EndBlock(); //----------- //Perform a valid Stake call var stakeAmount = accountBalance; var startingSoulBalance = simulator.Nexus.RootChain.GetTokenBalance(Nexus.StakingTokenSymbol, testUser.Address); simulator.BeginBlock(); tx = simulator.GenerateCustomTransaction(testUser, () => ScriptUtils.BeginScript().AllowGas(testUser.Address, Address.Null, 1, 9999) .CallContract("energy", "Stake", testUser.Address, stakeAmount). SpendGas(testUser.Address).EndScript()); simulator.EndBlock(); BigInteger stakedAmount = (BigInteger)simulator.Nexus.RootChain.InvokeContract("energy", "GetStake", testUser.Address); Assert.IsTrue(stakedAmount == stakeAmount); var finalSoulBalance = simulator.Nexus.RootChain.GetTokenBalance(Nexus.StakingTokenSymbol, testUser.Address); Assert.IsTrue(stakeAmount == startingSoulBalance - finalSoulBalance); //----------- //Upload a file: should succeed var filename = "notAVirus.exe"; var headerSize = CalculateRequiredSize(filename, 0); var contentSize = (long)(Archive.MaxSize) - (long)headerSize; var content = new byte[contentSize]; var contentMerkle = new MerkleTree(content); simulator.BeginBlock(); tx = simulator.GenerateCustomTransaction(testUser, () => ScriptUtils.BeginScript().AllowGas(testUser.Address, Address.Null, 1, 9999) .CallContract("storage", "UploadFile", testUser.Address, filename, contentSize, contentMerkle, ArchiveFlags.None, new byte[0]). SpendGas(testUser.Address).EndScript()); System.IO.File.WriteAllText(@"D:\Repos\bug_vm.txt", string.Join('\n', new VM.Disassembler(tx.Script).Instructions)); simulator.EndBlock(); var usedSpace = (BigInteger)simulator.Nexus.RootChain.InvokeContract("storage", "GetUsedSpace", testUser.Address); Assert.IsTrue(usedSpace == 0); }
public static BigInteger CalculateStorageSizeForStake(BigInteger stakeAmount) { var availableSize = stakeAmount * KilobytesPerStake * 1024; availableSize /= UnitConversion.GetUnitValue(Nexus.StakingTokenDecimals); return(availableSize); }
public BigInteger CalculateStorageSizeForStake(BigInteger stakeAmount) { var kilobytesPerStake = (int)Runtime.GetGovernanceValue(StorageContract.KilobytesPerStakeTag); var availableSize = stakeAmount * kilobytesPerStake * 1024; availableSize /= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals); return(availableSize); }
public void RegisterName(Address target, string name) { Runtime.Expect(target.IsUser, "must be user address"); Runtime.Expect(target != Runtime.GenesisAddress, "address must not be genesis"); Runtime.Expect(Runtime.IsWitness(target), "invalid witness"); Runtime.Expect(ValidationUtils.IsValidIdentifier(name), "invalid name"); var stake = Runtime.GetStake(target); Runtime.Expect(stake >= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals), "must have something staked"); Runtime.Expect(name != Runtime.NexusName, "name already used for nexus"); Runtime.Expect(!Runtime.ChainExists(name), "name already used for a chain"); Runtime.Expect(!Runtime.PlatformExists(name), "name already used for a platform"); Runtime.Expect(!Runtime.ContractExists(name), "name already used for a contract"); Runtime.Expect(!Runtime.FeedExists(name), "name already used for a feed"); Runtime.Expect(!Runtime.OrganizationExists(name), "name already used for a organization"); Runtime.Expect(!Runtime.TokenExists(name.ToUpper()), "name already used for a token"); Runtime.Expect(!_addressMap.ContainsKey(target), "address already has a name"); Runtime.Expect(!_nameMap.ContainsKey(name), "name already used for other account"); //System.Console.WriteLine("Trying to register: " + name); bool isReserved = false; for (int i = 0; i < prefixNames.Length; i++) { if (name.StartsWith(prefixNames[i])) { //System.Console.WriteLine("Starts with : " + prefixNames[i]+ " at index " +i); isReserved = true; break; } } for (int i = 0; i < reservedNames.Length; i++) { if (name == reservedNames[i]) { //System.Console.WriteLine("Reserved with : " + reservedNames[i]); isReserved = true; break; } } if (isReserved && Runtime.IsWitness(Runtime.GenesisAddress)) { isReserved = false; } Runtime.Expect(!isReserved, $"name '{name}' reserved by system"); _addressMap.Set(target, name); _nameMap.Set(name, target); Runtime.Notify(EventKind.AddressRegister, target, name); }
public void TransferScriptMethodExtraction() { var source = PhantasmaKeys.Generate(); var dest = PhantasmaKeys.Generate(); var amount = UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals); var script = ScriptUtils.BeginScript().AllowGas(source.Address, Address.Null, 1, 999).TransferTokens(DomainSettings.StakingTokenSymbol, source.Address, dest.Address, amount).SpendGas(source.Address).EndScript(); var table = DisasmUtils.GetDefaultDisasmTable(); var methods = DisasmUtils.ExtractMethodCalls(script, table); Assert.IsTrue(methods != null && methods.Count() == 3); }
public void DepositTokens(Address from, string symbol, BigInteger amount) { Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); Runtime.Expect(from.IsUser, "address must be user address"); Runtime.Expect(IsSupportedToken(symbol), "token is unsupported"); var info = Runtime.GetToken(symbol); var unitAmount = UnitConversion.GetUnitValue(info.Decimals); Runtime.Expect(amount >= unitAmount, "invalid amount"); Runtime.TransferTokens(symbol, from, this.Address, amount); }
// returns value in FIAT token public BigInteger GetTokenPrice(string symbol) { if (symbol == DomainSettings.FiatTokenSymbol) { return(UnitConversion.GetUnitValue(DomainSettings.FiatTokenDecimals)); } Core.Throw.If(!Nexus.TokenExists(RootStorage, symbol), "cannot read price for invalid token"); var token = GetToken(symbol); Core.Throw.If(Oracle == null, "cannot read price from null oracle"); var bytes = Oracle.Read(this.Time, "price://" + symbol); var value = BigInteger.FromUnsignedArray(bytes, true); Expect(value > 0, "token price not available for " + symbol); return(value); }
public void DepositTokens(Address from, string symbol, BigInteger amount) { Runtime.Expect(IsWitness(from), "invalid witness"); Runtime.Expect(from.IsUser, "address must be user address"); Runtime.Expect(IsSupportedToken(symbol), "token is unsupported"); var info = Runtime.Nexus.GetTokenInfo(symbol); var unitAmount = UnitConversion.GetUnitValue(info.Decimals); Runtime.Expect(amount >= unitAmount, "invalid amount"); Runtime.Expect(Runtime.Nexus.TransferTokens(Runtime, symbol, from, this.Address, amount), "tokens transfer failed"); Runtime.Notify(EventKind.TokenSend, from, new TokenEventData() { chainAddress = this.Address, symbol = symbol, value = amount }); }
public void RegisterScript(Address target, byte[] script, byte[] abiBytes) { Runtime.Expect(target.IsUser, "must be user address"); Runtime.Expect(target != Runtime.GenesisAddress, "address must not be genesis"); Runtime.Expect(Runtime.IsWitness(target), "invalid witness"); var stake = Runtime.GetStake(target); Runtime.Expect(stake >= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals), "must have something staked"); Runtime.Expect(script.Length < 1024, "invalid script length"); Runtime.Expect(!_scriptMap.ContainsKey(target), "address already has a script"); var abi = ContractInterface.FromBytes(abiBytes); Runtime.Expect(abi.MethodCount > 0, "unexpected empty contract abi"); var witnessTriggerName = AccountTrigger.OnWitness.ToString(); if (abi.HasMethod(witnessTriggerName)) { var witnessCheck = Runtime.InvokeTrigger(false, script, NativeContractKind.Account, abi, witnessTriggerName, Address.Null) != TriggerResult.Failure; Runtime.Expect(!witnessCheck, "script does not handle OnWitness correctly, case #1"); witnessCheck = Runtime.InvokeTrigger(false, script, NativeContractKind.Account, abi, witnessTriggerName, target) != TriggerResult.Failure; Runtime.Expect(witnessCheck, "script does not handle OnWitness correctly, case #2"); } _scriptMap.Set(target, script); _abiMap.Set(target, abiBytes); var constructor = abi.FindMethod(SmartContract.ConstructorName); if (constructor != null) { Runtime.CallContext(target.Text, constructor, target); } // TODO? Runtime.Notify(EventKind.AddressRegister, target, script); }
// returns value in FIAT token public BigInteger GetTokenPrice(string symbol) { if (symbol == Nexus.FiatTokenSymbol) { return(UnitConversion.GetUnitValue(Nexus.FiatTokenDecimals)); } if (symbol == Nexus.FuelTokenSymbol) { var result = GetTokenPrice(Nexus.StakingTokenSymbol); result /= 5; return(result); } Core.Throw.If(Oracle == null, "cannot read price from null oracle"); Core.Throw.If(!Nexus.TokenExists(symbol), "cannot read price for invalid token"); var bytes = Oracle.Read("price://" + symbol); var value = BigInteger.FromUnsignedArray(bytes, true); return(value); }
public void RegisterName(Address target, string name) { Runtime.Expect(target.IsUser, "must be user address"); Runtime.Expect(target != Runtime.GenesisAddress, "address must not be genesis"); Runtime.Expect(Runtime.IsWitness(target), "invalid witness"); Runtime.Expect(ValidationUtils.IsValidIdentifier(name), "invalid name"); var stake = Runtime.GetStake(target); Runtime.Expect(stake >= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals), "must have something staked"); Runtime.Expect(name != Runtime.NexusName, "name already used for nexus"); Runtime.Expect(!Runtime.ChainExists(name), "name already used for a chain"); Runtime.Expect(!Runtime.PlatformExists(name), "name already used for a platform"); Runtime.Expect(!Runtime.ContractExists(name), "name already used for a contract"); Runtime.Expect(!Runtime.FeedExists(name), "name already used for a feed"); Runtime.Expect(!Runtime.OrganizationExists(name), "name already used for a organization"); Runtime.Expect(!Runtime.TokenExists(name.ToUpper()), "name already used for a token"); Runtime.Expect(!_addressMap.ContainsKey(target), "address already has a name"); Runtime.Expect(!_nameMap.ContainsKey(name), "name already used for other account"); var isReserved = ValidationUtils.IsReservedIdentifier(name); if (isReserved && Runtime.IsWitness(Runtime.GenesisAddress)) { isReserved = false; } Runtime.Expect(!isReserved, $"name '{name}' reserved by system"); _addressMap.Set(target, name); _nameMap.Set(name, target); Runtime.Notify(EventKind.AddressRegister, target, name); }
// anyone can call this, not only manager, in order to be able to trigger refunds public void CloseSale(Address from, Hash saleHash) { Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); Runtime.Expect(_saleMap.ContainsKey(saleHash), "sale does not exist or already closed"); var sale = _saleMap.Get <Hash, SaleInfo>(saleHash); Runtime.Expect(Runtime.Time > sale.EndDate, "sale still not reached end date"); var soldSupply = _saleSupply.Get <Hash, BigInteger>(saleHash); var buyerAddresses = GetSaleParticipants(saleHash); var amountMap = _buyerAmounts.Get <Hash, StorageMap>(saleHash); var saleToken = Runtime.GetToken(sale.SellSymbol); var receiveToken = Runtime.GetToken(sale.ReceiveSymbol); if (soldSupply >= sale.GlobalSoftCap) // if at least soft cap reached, send tokens to buyers and funds to sellers { foreach (var addr in buyerAddresses) { var buyer = addr; var amount = amountMap.Get <Address, BigInteger>(buyer); Runtime.Notify(EventKind.Crowdsale, buyer, new SaleEventData() { kind = SaleEventKind.Distribution, saleHash = saleHash }); if (Runtime.ProtocolVersion <= 5) { buyer = sale.Creator; } Runtime.TransferTokens(sale.SellSymbol, this.Address, buyer, amount); } var fundsAmount = Runtime.ConvertBaseToQuote(soldSupply, UnitConversion.GetUnitValue(receiveToken.Decimals), saleToken, receiveToken); fundsAmount /= sale.Price; Runtime.Notify(EventKind.Crowdsale, sale.Creator, new SaleEventData() { kind = SaleEventKind.Distribution, saleHash = saleHash }); Runtime.TransferTokens(sale.ReceiveSymbol, this.Address, sale.Creator, fundsAmount); var leftovers = sale.GlobalHardCap - soldSupply; Runtime.TransferTokens(sale.SellSymbol, this.Address, sale.Creator, leftovers); } else // otherwise return funds to buyers and return tokens to sellers { foreach (var buyer in buyerAddresses) { var amount = amountMap.Get <Address, BigInteger>(buyer); amount = Runtime.ConvertBaseToQuote(amount, sale.Price, saleToken, receiveToken); Runtime.Notify(EventKind.Crowdsale, buyer, new SaleEventData() { kind = SaleEventKind.Refund, saleHash = saleHash }); Runtime.TransferTokens(sale.ReceiveSymbol, this.Address, buyer, amount); } Runtime.Notify(EventKind.Crowdsale, sale.Creator, new SaleEventData() { kind = SaleEventKind.Refund, saleHash = saleHash }); Runtime.TransferTokens(sale.SellSymbol, this.Address, sale.Creator, sale.GlobalHardCap); } }
public void TestAutoSendAddress() { var test = CreateAPI(); var simulator = test.simulator; var owner = test.owner; var testUser = PhantasmaKeys.Generate(); var autoSender = PhantasmaKeys.Generate(); var receiver = PhantasmaKeys.Generate(); var node = PhantasmaKeys.FromWIF(nodeWIF); var nexus = simulator.Nexus; var api = test.api; var symbol = DomainSettings.StakingTokenSymbol; var senderAddressStr = Base16.Encode(autoSender.Address.ToByteArray()); var receivingAddressStr = Base16.Encode(receiver.Address.ToByteArray()); var isTrue = true; string[] scriptString = new string[] { $"alias r1, $triggerReceive", $"alias r2, $currentTrigger", $"alias r3, $comparisonResult", $"alias r13, $triggerWitness", $"alias r14, $currentAddress", $"alias r15, $sourceAddress", $@"load $triggerReceive, ""{AccountTrigger.OnReceive}""", $@"load $triggerWitness, ""{AccountTrigger.OnWitness}""", $"pop $currentTrigger", $"pop $currentAddress", $"equal $triggerWitness, $currentTrigger, $comparisonResult", $"jmpif $comparisonResult, @witnessHandler", $"equal $triggerReceive, $currentTrigger, $comparisonResult", $"jmpif $comparisonResult, @receiveHandler", $"jmp @end", $"@receiveHandler: nop", $"alias r4, $tokenContract", $"alias r5, $sourceAddress", $"alias r6, $targetAddress", $"alias r7, $receivedAmount", $"alias r8, $symbol", $"alias r9, $methodName", $"pop $sourceAddress", $"pop $symbol", $"pop $receivedAmount", $"load r11 0x{receivingAddressStr}", $"push r11", $@"extcall ""Address()""", $"pop $targetAddress", $"push $receivedAmount", $"push $symbol", $"push $targetAddress", $"push $sourceAddress", "extcall \"Runtime.TransferTokens\"", $"@witnessHandler: ", $"load r11 0x{senderAddressStr}", $"push r11", "extcall \"Address()\"", $"pop $sourceAddress", $"equal $sourceAddress, $currentAddress, $comparisonResult", "jmpif $comparisonResult, @endWitness", "throw", $"@endWitness: ret", $"load r11 {isTrue}", $"push r11", $"@end: ret" }; var script = AssemblerUtils.BuildScript(scriptString); simulator.BeginBlock(); simulator.GenerateTransfer(owner, autoSender.Address, simulator.Nexus.RootChain, DomainSettings.FuelTokenSymbol, 100000); simulator.GenerateTransfer(owner, autoSender.Address, simulator.Nexus.RootChain, DomainSettings.StakingTokenSymbol, UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals)); simulator.GenerateCustomTransaction(autoSender, ProofOfWork.None, () => ScriptUtils.BeginScript().AllowGas(autoSender.Address, Address.Null, 1, 9999) .CallContract("stake", "Stake", autoSender.Address, UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals)) .CallContract("account", "RegisterScript", autoSender.Address, script).SpendGas(autoSender.Address) .EndScript()); simulator.EndBlock(); simulator.BeginBlock(); simulator.GenerateTransfer(owner, autoSender.Address, simulator.Nexus.RootChain, symbol, 30000); simulator.EndBlock(); var token = simulator.Nexus.GetTokenInfo(simulator.Nexus.RootStorage, symbol); var balance = simulator.Nexus.RootChain.GetTokenBalance(simulator.Nexus.RootStorage, token, receiver.Address); Assert.IsTrue(balance == 30000); }
public void Purchase(Address from, Hash saleHash, string quoteSymbol, BigInteger quoteAmount) { //For now, prevent purchases with other tokens Runtime.Expect(quoteSymbol == DomainSettings.StakingTokenSymbol, "invalid receive token symbol: " + quoteSymbol + ". SOUL token must be used for purchase"); Runtime.Expect(Runtime.TokenExists(quoteSymbol), "token must exist: " + quoteSymbol); var quoteToken = Runtime.GetToken(quoteSymbol); Runtime.Expect(_saleMap.ContainsKey <Hash>(saleHash), "sale does not exist"); var sale = _saleMap.Get <Hash, SaleInfo>(saleHash); Runtime.Expect(Runtime.Time >= sale.StartDate, "sale has not started"); Runtime.Expect(Runtime.Time < sale.EndDate, "sale has reached end date"); Runtime.Expect(quoteSymbol != sale.SellSymbol, "cannot participate in the sale using " + quoteSymbol); Runtime.Expect(from != sale.Creator, "sale creator can't participate"); Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); if (sale.Flags.HasFlag(SaleFlags.Whitelist)) { Runtime.Expect(IsWhitelisted(saleHash, from), "address is not whitelisted"); } var saleToken = Runtime.GetToken(sale.SellSymbol); var convertedAmount = Runtime.ConvertQuoteToBase(quoteAmount, UnitConversion.GetUnitValue(quoteToken.Decimals), saleToken, quoteToken) * sale.Price; var temp = UnitConversion.ToDecimal(convertedAmount, saleToken.Decimals); Runtime.Expect(temp >= 1, "cannot purchase very tiny amount"); var previousSupply = _saleSupply.Get <Hash, BigInteger>(saleHash); var nextSupply = previousSupply + convertedAmount; //Runtime.Expect(nextSupply <= sale.HardCap, "hard cap reached"); if (nextSupply > sale.GlobalHardCap) { convertedAmount = sale.GlobalHardCap - previousSupply; Runtime.Expect(convertedAmount > 0, "hard cap reached"); quoteAmount = Runtime.ConvertBaseToQuote(convertedAmount, sale.Price, saleToken, quoteToken); nextSupply = 0; } Runtime.TransferTokens(quoteSymbol, from, this.Address, quoteAmount); Runtime.Notify(EventKind.Crowdsale, from, new SaleEventData() { kind = SaleEventKind.Participation, saleHash = saleHash }); _saleSupply.Set <Hash, BigInteger>(saleHash, nextSupply); if (nextSupply == 0) { Runtime.Notify(EventKind.Crowdsale, from, new SaleEventData() { kind = SaleEventKind.HardCap, saleHash = saleHash }); } else if (previousSupply < sale.GlobalSoftCap && nextSupply >= sale.GlobalSoftCap) { Runtime.Notify(EventKind.Crowdsale, from, new SaleEventData() { kind = SaleEventKind.SoftCap, saleHash = saleHash }); } if (quoteSymbol != sale.ReceiveSymbol) { Runtime.CallNativeContext(NativeContractKind.Swap, nameof(SwapContract.SwapTokens), this.Address, quoteSymbol, sale.ReceiveSymbol, quoteAmount); } var amountMap = _buyerAmounts.Get <Hash, StorageMap>(saleHash); var totalAmount = amountMap.Get <Address, BigInteger>(from); var newAmount = totalAmount + convertedAmount; if (sale.UserSoftCap > 0) { Runtime.Expect(newAmount >= sale.UserSoftCap, "user purchase minimum limit not reached"); } if (sale.UserHardCap > 0) { Runtime.Expect(newAmount <= sale.UserHardCap, "user purchase maximum limit exceeded"); } var addressMap = _buyerAddresses.Get <Hash, StorageList>(saleHash); if (!addressMap.Contains <Address>(from)) { addressMap.Add <Address>(from); } amountMap.Set <Address, BigInteger>(from, newAmount); }
// send to external chain public void WithdrawTokens(Address from, Address to, string symbol, BigInteger amount) { Runtime.Expect(amount > 0, "amount must be positive and greater than zero"); Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); Runtime.Expect(from.IsUser, "source must be user address"); Runtime.Expect(to.IsInterop, "destination must be interop address"); Runtime.Expect(Runtime.TokenExists(symbol), "invalid token"); var transferTokenInfo = this.Runtime.GetToken(symbol); Runtime.Expect(transferTokenInfo.Flags.HasFlag(TokenFlags.Transferable), "transfer token must be transferable"); Runtime.Expect(transferTokenInfo.Flags.HasFlag(TokenFlags.External), "transfer token must be external"); Runtime.Expect(transferTokenInfo.Flags.HasFlag(TokenFlags.Fungible), "transfer token must be fungible"); string platform; byte[] dummy; to.DecodeInterop(out platform, out dummy, 0); Runtime.Expect(platform != DomainSettings.PlatformName, "must be external platform"); Runtime.Expect(Runtime.PlatformExists(platform), "invalid platform"); var platformInfo = Runtime.GetPlatform(platform); Runtime.Expect(to != platformInfo.Address, "invalid target address"); var feeSymbol = platformInfo.Symbol; Runtime.Expect(Runtime.TokenExists(feeSymbol), "invalid fee token"); var feeTokenInfo = this.Runtime.GetToken(feeSymbol); Runtime.Expect(feeTokenInfo.Flags.HasFlag(TokenFlags.Fungible), "fee token must be fungible"); Runtime.Expect(feeTokenInfo.Flags.HasFlag(TokenFlags.Transferable), "fee token must be transferable"); var basePrice = UnitConversion.GetUnitValue(DomainSettings.FiatTokenDecimals) / InteropFeeRacio; // 50cents var feeAmount = Runtime.GetTokenQuote(DomainSettings.FiatTokenSymbol, feeSymbol, basePrice); Runtime.Expect(feeAmount > 0, "fee is too small"); Runtime.Expect(Runtime.TransferTokens(feeSymbol, from, this.Address, feeAmount), "fee transfer failed"); Runtime.Expect(Runtime.TransferTokens(symbol, from, platformInfo.Address, amount), "burn failed"); var collateralAmount = Runtime.GetTokenQuote(DomainSettings.FiatTokenSymbol, DomainSettings.FuelTokenSymbol, basePrice); var withdraw = new InteropWithdraw() { destination = to, transferAmount = amount, transferSymbol = symbol, feeAmount = feeAmount, feeSymbol = feeSymbol, hash = Runtime.Transaction.Hash, broker = Address.Null, collateralAmount = collateralAmount, timestamp = Runtime.Time }; _withdraws.Add <InteropWithdraw>(withdraw); Runtime.Notify(EventKind.TokenSend, from, new TokenEventData() { chainAddress = this.Address, value = amount, symbol = symbol }); Runtime.Notify(EventKind.TokenEscrow, from, new TokenEventData() { chainAddress = this.Address, value = feeAmount, symbol = symbol }); Runtime.Notify(EventKind.BrokerRequest, from, to); }