Example #1
0
        public static object HandleHelperOperation(string operation, params object[] args)
        {
            switch (operation)
            {
            case "BalanceOfVestedAddress":
                // retrieve the real balance of an address that has been subjected to whitepaper defined vesting period
                if (!Helpers.RequireArgumentLength(args, 1))
                {
                    return(false);
                }
                return(Helpers.BalanceOfVestedAddress((byte[])args[0]));

            case "IsPrivateSaleAllocationLocked":
                // if the admin method `Administration.AllocatePresalePurchase` is permanently disabled, this method will return
                // the timestamp the lock was put in place.
                return(Storage.Get(Storage.CurrentContext, StorageKeys.PrivateSaleAllocationLocked()));

            case "supportedStandards":
                // support NEP-10 by responding to supportedStandards
                // https://github.com/neo-project/proposals/blob/master/nep-10.mediawiki
                return(ICOTemplate.SupportedStandards());

            case "BalanceOfSaleContribution":
                if (!Helpers.RequireArgumentLength(args, 1))
                {
                    return(false);
                }
                return(Helpers.BalanceOfSaleContribution((byte[])args[0]));
            }
            return(false);
        }
        /// <summary>
        /// Claims unsold tokens
        /// </summary>
        /// <returns></returns>
        public static bool ClaimUnsoldTokens()
        {
            bool UnsoldTokensClaimed = Storage.Get(Storage.CurrentContext, StorageKeys.UnsoldTokensClaimed()).AsString() == "1";

            //This method can only be executed by the admin account, after the public sale, and can only be called once (use UnsoldTokensClaimed() storage item)
            if (Helpers.GetBlockTimestamp() >= ICOTemplate.PublicSaleEndTime() && UnsoldTokensClaimed == false && Helpers.VerifyIsAdminAccount())
            {
                byte[] address = ICOTemplate.AdditionalCompanyTokenFund;

                //Get amount remaining
                BigInteger amountRemaining = NEP5.CrowdsaleAvailableAmount();

                //Add vested amount to account
                TokenSale.SetVestingPeriodForAddress(address, "company", amountRemaining);

                //Set total supply
                Helpers.SetTotalSupply(amountRemaining);

                //Set the UnsoldTokensClaimed() storage item so ClaimUnsoldTokens() cannot be called again
                Storage.Put(Storage.CurrentContext, StorageKeys.UnsoldTokensClaimed(), "1");

                transfer(null, address, amountRemaining);

                Runtime.Notify("ClaimUnsoldTokens() tokens allocated", address, amountRemaining);

                return(true);
            }

            return(false);
        }
        /// <summary>
        /// mint tokens is called when a user wishes to purchase tokens
        /// </summary>
        /// <returns></returns>
        public static bool MintTokens()
        {
            object[]    transactionData = Helpers.GetTransactionAndSaleData();
            Transaction tx = (Transaction)transactionData[0];

            byte[]     sender                    = (byte[])transactionData[1];
            byte[]     receiver                  = (byte[])transactionData[2];
            ulong      receivedNEO               = (ulong)transactionData[3];
            ulong      receivedGAS               = (ulong)transactionData[4];
            BigInteger whiteListGroupNumber      = (BigInteger)transactionData[5];
            BigInteger crowdsaleAvailableAmount  = (BigInteger)transactionData[6];
            BigInteger groupMaximumContribution  = (BigInteger)transactionData[7];
            BigInteger totalTokensPurchased      = (BigInteger)transactionData[8];
            BigInteger neoRemainingAfterPurchase = (BigInteger)transactionData[9];
            BigInteger gasRemainingAfterPurchase = (BigInteger)transactionData[10];
            BigInteger totalContributionBalance  = (BigInteger)transactionData[11];

            if (Helpers.GetBlockTimestamp() >= ICOTemplate.PublicSaleEndTime())
            {
                Runtime.Notify("MintTokens() failed. Token Sale is closed.", false);
                return(false);
            }

            if (!CanUserParticipateInSale(transactionData))
            {
                Runtime.Notify("MintTokens() CanUserParticipate failed", false);
                return(false);
            }

            byte[] lastTransactionHash = Storage.Get(Storage.CurrentContext, StorageKeys.MintTokensLastTX());
            if (lastTransactionHash == tx.Hash)
            {
                // ensure that minTokens doesnt process the same transaction more than once
                Runtime.Notify("MintTokens() not processing duplicate tx.Hash", tx.Hash);
                return(false);
            }

            Storage.Put(Storage.CurrentContext, StorageKeys.MintTokensLastTX(), tx.Hash);
            Runtime.Notify("MintTokens() receivedNEO / receivedGAS", receivedNEO, receivedGAS);

            if (neoRemainingAfterPurchase > 0 || gasRemainingAfterPurchase > 0)
            {
                // this purchase would have exceed the allowed max supply so we spent what we could and will refund the remainder
                refund(sender, neoRemainingAfterPurchase, gasRemainingAfterPurchase);
            }

            BigInteger senderAmountSubjectToVesting = SubjectToVestingPeriod(sender);
            BigInteger newTokenBalance = NEP5.BalanceOf(sender) + totalTokensPurchased + senderAmountSubjectToVesting;

            Helpers.SetBalanceOf(sender, newTokenBalance);
            Helpers.SetBalanceOfSaleContribution(sender, totalContributionBalance);
            Helpers.SetTotalSupply(totalTokensPurchased);

            transfer(null, sender, totalTokensPurchased);
            return(true);
        }
Example #4
0
        /// <summary>
        /// allow an administrator to request the unlocking of founder tokens
        /// </summary>
        /// <param name="address">founders script hash</param>
        /// <param name="roundNumber">1-7</param>
        /// <returns></returns>
        public static bool UnlockFoundersTokens(byte[] address, int roundNumber)
        {
            if (address.Length != 20)
            {
                Runtime.Log("UnlockFoundersTokens() invalid address supplied");
                return(false);
            }

            byte[]     roundKey       = address.Concat(((BigInteger)roundNumber).AsByteArray());
            StorageMap unlockedRounds = Storage.CurrentContext.CreateMap(StorageKeys.FounderTokenUnlockRound());

            bool roundPreviouslyUnlocked = unlockedRounds.Get(roundKey).AsBigInteger() > 0;

            if (roundPreviouslyUnlocked)
            {
                Runtime.Log("UnlockFoundersTokens() round already unlocked");
                return(false);
            }

            object[] foundersVestingPeriod = GetCoreTeamVestingSchedule();

            uint currentTimestamp = Helpers.GetBlockTimestamp();
            int  roundIndex       = (roundNumber * 2) - 2;
            int  roundValueIndex  = roundIndex + 1;

            if (roundIndex < 0)
            {
                Runtime.Log("UnlockFoundersTokens() invalid round index (<0)");
                return(false);
            }

            uint       roundReleaseDate   = (uint)foundersVestingPeriod[roundIndex];
            BigInteger roundReleaseAmount = (BigInteger)foundersVestingPeriod[roundValueIndex];

            if (currentTimestamp < roundReleaseDate)
            {
                Runtime.Log("UnlockFoundersTokens() not scheduled for release");
                return(false);
            }


            object[] founderKeys = ICOTemplate.MoonlightFounderKeys();
            for (int i = 0; i < founderKeys.Length; i++)
            {
                byte[] founderKey = (byte[])founderKeys[i];
                if (founderKey == address)
                {
                    Runtime.Notify("UnlockFoundersTokens() releasing funds. currentTimestamp / roundReleaseDate / roundReleaseAmount", currentTimestamp, roundReleaseDate, roundReleaseAmount);
                    Helpers.SetBalanceOf(founderKey, NEP5.BalanceOf(founderKey) + roundReleaseAmount);            // set new balance for destination account
                    unlockedRounds.Put(roundKey, "1");
                    return(true);
                }
            }
            return(false);
        }
Example #5
0
        /// <summary>
        /// get the maximum number of LX that can be purchased by groupNumber during the public sale
        /// </summary>
        /// <param name="groupNumber"></param>
        /// <returns></returns>
        public static BigInteger GetGroupMaxContribution(BigInteger groupNumber)
        {
            StorageMap contributionLimits = Storage.CurrentContext.CreateMap(StorageKeys.GroupContributionAmountPrefix());
            BigInteger maxContribution    = contributionLimits.Get(groupNumber.AsByteArray()).AsBigInteger();

            if (maxContribution > 0)
            {
                return(maxContribution);
            }

            return(ICOTemplate.MaximumContributionAmount());
        }
Example #6
0
        /// <summary>
        /// helper method to retrieve the stored group unlock block height
        /// </summary>
        /// <param name="groupNumber"></param>
        /// <returns></returns>
        public static uint GetGroupUnlockTime(BigInteger groupNumber)
        {
            BigInteger unlockTime = 0;

            if (groupNumber <= 0 || groupNumber > 4)
            {
                return(0);
            }
            else if (groupNumber > 0 && groupNumber <= 4)
            {
                unlockTime = (uint)ICOTemplate.PresaleStartTime();
            }
            return((uint)unlockTime);
        }
        /// <summary>
        /// MintTokensEth is called when a the ETH contribution listener server triggers an Ether receive event
        /// </summary>
        /// <returns></returns>
        public static bool MintTokensEth(string ethAddress, byte[] neoAddress, ulong ethReceived)
        {
            object[]    transactionData = Helpers.GetEthTransactionAndSaleData(ethReceived, ethAddress, neoAddress);
            Transaction tx = (Transaction)transactionData[0];

            byte[]     sender                   = (byte[])transactionData[1];
            byte[]     receiver                 = (byte[])transactionData[2];
            BigInteger whiteListGroupNumber     = (BigInteger)transactionData[5];
            BigInteger crowdsaleAvailableAmount = (BigInteger)transactionData[6];
            BigInteger groupMaximumContribution = (BigInteger)transactionData[7];
            BigInteger totalTokensPurchased     = (BigInteger)transactionData[8] * NEP5.factor;
            BigInteger totalContributionBalance = (BigInteger)transactionData[9];

            if (!CanETHUserParticipateInSale(transactionData))
            {
                refundEth(ethAddress, ethReceived);
                Runtime.Notify("MintTokensEth() CanUserParticipate failed", false);
                return(false);
            }

            if (Helpers.GetBlockTimestamp() >= ICOTemplate.PublicSaleEndTime())
            {
                refundEth(ethAddress, ethReceived);
                Runtime.Notify("MintTokensEth() failed. Token Sale is closed.", false);
                return(false);
            }

            byte[] lastTransactionHash = Storage.Get(Storage.CurrentContext, StorageKeys.MintTokensEthLastTX());
            if (lastTransactionHash == tx.Hash)
            {
                // ensure that minTokens doesnt process the same transaction more than once
                Runtime.Notify("MintTokensEth() not processing duplicate tx.Hash", tx.Hash);
                return(false);
            }

            BigInteger tokenTotalSupply = NEP5.TotalSupply();

            Storage.Put(Storage.CurrentContext, StorageKeys.MintTokensEthLastTX(), tx.Hash);
            Runtime.Notify("MintTokensEth() receivedETH", ethReceived);

            BigInteger senderAmountSubjectToVesting = TokenSale.SubjectToVestingPeriod(sender);
            BigInteger newTokenBalance = NEP5.BalanceOf(sender) + totalTokensPurchased + senderAmountSubjectToVesting;

            Helpers.SetBalanceOf(sender, newTokenBalance);
            Helpers.SetBalanceOfSaleContribution(sender, totalContributionBalance);
            Helpers.SetTotalSupply(totalTokensPurchased);

            transfer(null, sender, totalTokensPurchased);
            return(true);
        }
Example #8
0
        /// <summary>
        /// initialise the smart contract for use
        /// </summary>
        /// <returns></returns>
        public static bool InitSmartContract()
        {
            if (Helpers.ContractInitialised())
            {
                // contract can only be initialised once
                Runtime.Log("InitSmartContract() contract already initialised");
                return(false);
            }

            uint ContractInitTime = Helpers.GetBlockTimestamp();

            Storage.Put(Storage.CurrentContext, StorageKeys.ContractInitTime(), ContractInitTime);

            // assign pre-allocated tokens to the project
            object[] immediateAllocation = ICOTemplate.ImmediateProjectGrowthAllocation();
            object[] vestedAllocation    = ICOTemplate.VestedProjectGrowthAllocation();

            BigInteger immediateProjectAllocationValue = ((ICOTemplate.TokenMaxSupply * (BigInteger)immediateAllocation[0]) / 100) * NEP5.factor;
            BigInteger vestedProjectAllocationValue    = ((ICOTemplate.TokenMaxSupply * (BigInteger)vestedAllocation[0]) / 100) * NEP5.factor;

            Helpers.SetBalanceOf(ICOTemplate.MoonlightProjectKey(), immediateProjectAllocationValue + vestedProjectAllocationValue);
            Helpers.SetBalanceOfVestedAmount(ICOTemplate.MoonlightProjectKey(), immediateProjectAllocationValue + vestedProjectAllocationValue);

            // lockup a portion of the tokens to be released in the future
            uint vestedGrowthReleaseDate = (uint)vestedAllocation[1] + ContractInitTime;

            object[]   vestedTokenPeriod = new object[] { vestedGrowthReleaseDate, vestedProjectAllocationValue };
            StorageMap vestingData       = Storage.CurrentContext.CreateMap(StorageKeys.VestedTokenPrefix());

            vestingData.Put(ICOTemplate.MoonlightProjectKey(), vestedTokenPeriod.Serialize());

            // token allocation to MoonlightFounderKeys - update the total supply to include balance - these funds will be unlocked gradually
            BigInteger founderTokenAllocation = ((ICOTemplate.TokenMaxSupply * (BigInteger)ICOTemplate.MoonlightFoundersAllocationPercentage()) / 100) * NEP5.factor;

            // token allocated to presale
            BigInteger presaleAllocationMaxValue = ((ICOTemplate.TokenMaxSupply * (BigInteger)ICOTemplate.PresaleAllocationPercentage()) / 100) * NEP5.factor;

            // update the total supply to reflect the project allocated tokens
            BigInteger totalSupply = immediateProjectAllocationValue + vestedProjectAllocationValue + founderTokenAllocation + presaleAllocationMaxValue;

            Helpers.SetTotalSupply(totalSupply);

            UpdateAdminAddress(ICOTemplate.InitialAdminAccount);
            EnableDEXWhitelisting(ICOTemplate.WhitelistDEXListings());
            Runtime.Log("InitSmartContract() contract initialisation complete");
            return(true);
        }
Example #9
0
        /// <summary>
        /// set a vesting schedule, as defined in the whitepaper, for tokens purchased during the presale
        /// </summary>
        /// <param name="address"></param>
        /// <param name="tokenBalance"></param>
        /// <returns></returns>
        public static bool SetVestingPeriodForAddress(byte[] address, BigInteger tokensPurchased)
        {
            if (!ICOTemplate.UseTokenVestingPeriod())
            {
                return(false);
            }

            if (address.Length != 20)
            {
                return(false);
            }

            object[]   vestingOne              = ICOTemplate.VestingBracketOne();
            object[]   vestingTwo              = ICOTemplate.VestingBracketTwo();
            BigInteger bracketOneThreshold     = (BigInteger)vestingOne[0] * NEP5.factor;
            BigInteger bracketTwoThreshold     = (BigInteger)vestingTwo[0] * NEP5.factor;
            BigInteger currentAvailableBalance = 0;        // how many tokens will be immediately available to the owner

            uint       currentTimestamp      = Helpers.GetContractInitTime();
            uint       bracketOneReleaseDate = (uint)vestingOne[1] + currentTimestamp;
            uint       bracketTwoReleaseDate = (uint)vestingTwo[1] + currentTimestamp;
            StorageMap vestingData           = Storage.CurrentContext.CreateMap(StorageKeys.VestedTokenPrefix());

            if (tokensPurchased > bracketTwoThreshold)
            {
                // user has purchased enough tokens to fall under the second vesting period restriction
                // calculate the difference between the bracketOne and bracketTwo thresholds to calculate how much should be released after bracketOne lapses
                BigInteger bracketOneReleaseAmount = bracketTwoThreshold - bracketOneThreshold;
                // the remainder will be released after the bracket two release date
                BigInteger bracketTwoReleaseAmount = tokensPurchased - bracketOneReleaseAmount - bracketOneThreshold;
                object[]   lockoutTimes            = new object[] { bracketOneReleaseDate, bracketOneReleaseAmount, bracketTwoReleaseDate, bracketTwoReleaseAmount };
                vestingData.Put(address, lockoutTimes.Serialize());
            }
            else
            {
                // user has purchased enough tokens to fall under the first vesting period restriction
                // calculate the difference between amount purchased and bracketOne threshold to calculate how much should be released after the bracketOne lapses
                BigInteger bracketOneReleaseAmount = tokensPurchased - bracketOneThreshold;
                object[]   lockoutTimes            = new object[] { bracketOneReleaseDate, bracketOneReleaseAmount };
                vestingData.Put(address, lockoutTimes.Serialize());
            }

            // ensure the total amount purchased is saved
            Helpers.SetBalanceOf(address, tokensPurchased);
            Helpers.SetBalanceOfVestedAmount(address, tokensPurchased);
            return(true);
        }
Example #10
0
        /// <summary>
        /// retrieve information for the received transaction
        /// </summary>
        /// <returns>object[] {
        /// (Transaction)tx, (byte[])sender, (byte)receiver, ulong receivedNEO, ulong receivedGAS,
        /// (BigInteger)whiteListGroupNumber, (BigInteger)crowdsaleAvailableAmount, (BigInteger)groupMaximumContribution
        /// (BigInteger)totalTokensPurchased, (BigInteger)neoRemainingAfterPurchase, (BigInteger)gasRemainingAfterPurchase
        /// (BigInteger)totalContributionBalance
        /// }
        /// </returns>
        public static object[] GetEthTransactionAndSaleData(ulong receivedETH, string ethAddress, byte[] neoAddress)
        {
            Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;

            byte[] sender   = neoAddress;
            byte[] receiver = neoAddress;

            // only add funds to total received value if receiver is the recipient of the output
            Runtime.Notify("GetEthTransactionData() Received ETH Deposit type", receiver);

            BigInteger whiteListGroupNumber     = KYC.GetWhitelistGroupNumber(sender);
            BigInteger crowdsaleAvailableAmount = NEP5.CrowdsaleAvailableAmount();
            BigInteger groupMaximumContribution = KYC.GetGroupMaxContribution(whiteListGroupNumber) * NEP5.factor;

            BigInteger totalTokensPurchased = 0;

            //ETH minimum must be 0.1 eth
            if (ICOTemplate.ICOAllowsETH() && receivedETH >= ICOTemplate.EthMinimumContribution())
            {
                //Get the amount of tokens in exchange for contributed ETH. receivedETH is with 18 decimals so divide by 1000000000000000000.
                BigInteger ethTokenValue = receivedETH * ICOTemplate.ICOEthToTokenExchangeRate() / 1000000000000000000;

                // there is enough NOS left for this purchase to complete
                totalTokensPurchased = ethTokenValue;

                // ensure amountAvailable now reflects number of tokens purchased with ETH
            }

            BigInteger totalContributionBalance = BalanceOfSaleContribution(sender) + (totalTokensPurchased * NEP5.factor);

            return(new object[] {
                tx,                             // neo transaction object
                sender,                         // who initiated the transfer
                receiver,                       // who the assets were sent to
                ethAddress,                     // ETH address of contributor
                receivedETH,                    // how many neo were transferred
                whiteListGroupNumber,           // what whitelist group is the sender in
                crowdsaleAvailableAmount,       // how many tokens are left to be purchased
                groupMaximumContribution,       // how many tokens can members of this whitelist group purchase
                totalTokensPurchased,           // the total number of tokens purchased in this transaction
                totalContributionBalance        // the total amount of tokens sender has purchased during public sale
            });
        }
Example #11
0
        /// <summary>
        /// get the maximum number of NOS that can be purchased by groupNumber during the public sale
        /// </summary>
        /// <param name="groupNumber"></param>
        /// <returns></returns>
        public static BigInteger GetGroupMaxContribution(BigInteger groupNumber)
        {
            BigInteger maxContribution           = 0;
            uint       latestTimeStamp           = Helpers.GetBlockTimestamp();
            uint       publicSaleMaxContribution = (uint)ICOTemplate.MaximumContributionAmount();
            uint       publicSaleEndTime         = (uint)ICOTemplate.PublicSaleEndTime();

            //If latest block timestamp is larger than presale start and smaller than presale end: check presale tier contributions.
            if (latestTimeStamp >= (uint)ICOTemplate.PresaleStartTime() && latestTimeStamp <= (uint)ICOTemplate.PresaleEndTime())
            {
                //Presale has not ended. Only presale can participate.
                if (groupNumber == 1)
                {
                    //Pre-sale tier 1.
                    maxContribution = (uint)ICOTemplate.PresaleTierOne();
                }
                else if (groupNumber == 2)
                {
                    //Pre-sale tier 2.
                    maxContribution = (uint)ICOTemplate.PresaleTierTwo();
                }
                else if (groupNumber == 3)
                {
                    //Pre-sale tier 3.
                    maxContribution = (uint)ICOTemplate.PresaleTierThree();
                }
                else if (groupNumber == 4)
                {
                    //Tier 4
                    maxContribution = (uint)ICOTemplate.PresaleTierFour();
                }
            }
            //Otherwise we're in the public sale; get the publicSaleMaxContribution
            //publicSaleMaxContribution returns the max contribution based on the presale phase using Helpers.GetPublicSaleMaxContribution()
            else if (groupNumber > 0 && groupNumber <= 4 && latestTimeStamp >= (uint)ICOTemplate.PublicSaleStartTime() && latestTimeStamp <= publicSaleEndTime)
            {
                maxContribution = publicSaleMaxContribution;
            }

            return(maxContribution);
        }
        /// <summary>
        /// allow allocation of presale purchases by contract administrator. this allows the nOS team to allocate the nOS tokens from the private sale, company reserve, and locked incentive reserve.
        /// This method will not allow the private allocations to exceed the defined amount
        /// the state of the `LockPrivateSaleAllocation` can be determined by the public using the method `IsPrivateSaleAllocationLocked` (returns timestamp that lock was put in place)
        /// </summary>
        /// <param name="address"></param>
        /// <param name="amountPurchased"></param>
        /// <returns></returns>
        public static bool AllocatePrivateSalePurchase(byte[] address, string allocationType, BigInteger amountPurchased)
        {
            amountPurchased = amountPurchased * NEP5.factor;

            bool privateSaleLocked = Storage.Get(Storage.CurrentContext, StorageKeys.PrivateSaleAllocationLocked()).AsBigInteger() > 0;

            if (privateSaleLocked)
            {
                Runtime.Notify("AllocatePrivateSalePurchase() privateSaleLocked, can't allocate");
                return(false);
            }

            if (allocationType != "incentive" && allocationType != "privateSale" && allocationType != "company")
            {
                return(false);
            }

            BigInteger presaleAllocationMaxValue = ICOTemplate.LockedTokenAllocationAmount() * NEP5.factor;
            BigInteger presaleAllocatedValue     = Storage.Get(Storage.CurrentContext, StorageKeys.PresaleAllocatedValue()).AsBigInteger();

            if ((presaleAllocatedValue + amountPurchased) > presaleAllocationMaxValue)
            {
                // this purchase will exceed the presale cap.. dont allow
                Runtime.Notify("AllocatePrivateSalePurchase() purchase will exceed max allocation");
                return(false);
            }

            if (!TokenSale.SetVestingPeriodForAddress(address, allocationType, amountPurchased))
            {
                Runtime.Notify("SetVestingPeriodForAddress() failed.");
                return(false);
            }

            Storage.Put(Storage.CurrentContext, StorageKeys.PresaleAllocatedValue(), presaleAllocatedValue + amountPurchased);
            transfer(null, address, amountPurchased);

            Runtime.Notify("AllocatePrivateSalePurchase() tokens allocated", address, amountPurchased, allocationType);

            return(true);
        }
        /// <summary>
        /// initialise the smart contract for use
        /// </summary>
        /// <returns></returns>
        public static bool InitSmartContract()
        {
            if (Helpers.ContractInitialised())
            {
                // contract can only be initialised once
                Runtime.Log("InitSmartContract() contract already initialised");
                return(false);
            }


            uint ContractInitTime = Helpers.GetBlockTimestamp();

            Storage.Put(Storage.CurrentContext, StorageKeys.ContractInitTime(), ContractInitTime);

            // assign pre-allocated tokens to the NosProjectKey() (10,000,000 tokens)
            BigInteger immediateProjectAllocationValue = ICOTemplate.ImmediateCompanyReserve() * NEP5.factor;


            Helpers.SetBalanceOf(ICOTemplate.NosProjectKey, immediateProjectAllocationValue);
            transfer(null, ICOTemplate.NosProjectKey, immediateProjectAllocationValue);

            // token allocated to private sale & vested reserves & incentives
            BigInteger presaleAllocationMaxValue = ICOTemplate.LockedTokenAllocationAmount() * NEP5.factor;

            // update the total supply to reflect the project allocated tokens
            BigInteger totalSupply = immediateProjectAllocationValue + presaleAllocationMaxValue;

            Helpers.SetTotalSupply(totalSupply);

            UpdateAdminAddress(ICOTemplate.InitialAdminAccount);

            EnableTransferFromWhitelisting(ICOTemplate.WhitelistTransferFromListings());

            Runtime.Log("InitSmartContract() contract initialisation complete");
            return(true);
        }
Example #14
0
        /// <summary>
        /// the core teams token allocation follow a linear quarterly maturation over 18 months beginning after 6 months
        /// </summary>
        /// <returns></returns>
        public static object[] GetCoreTeamVestingSchedule()
        {
            // calculate the allocation given to each team member
            object[]   founderKeys            = ICOTemplate.MoonlightFounderKeys();
            BigInteger founderTokenAllocation = ((ICOTemplate.TokenMaxSupply * (BigInteger)ICOTemplate.MoonlightFoundersAllocationPercentage()) / 100) * NEP5.factor;
            BigInteger individualAllocation   = founderTokenAllocation / founderKeys.Length;

            uint ContractInitTime = Helpers.GetContractInitTime();
            // determine vesting schedule details for core teams token allocation
            // there will be 7 releases, one each quarter ending 2 years from contract init
            int        numberOfTokenReleases = 7;
            BigInteger tokensPerRelease      = individualAllocation / numberOfTokenReleases;

            object[] vestingPeriod          = new object[14];
            object[] founderReleaseSchedule = ICOTemplate.MoonlightFoundersAllocationReleaseSchedule();
            uint     initialReleaseDate     = ContractInitTime + (uint)founderReleaseSchedule[0];
            uint     releaseFrequency       = (uint)founderReleaseSchedule[1];

            BigInteger tokensReleased = tokensPerRelease;

            // this is not the nicest way to populate the vesting schedule array, but it is much cheaper (in terms of processing/gas price) than looping
            vestingPeriod[0] = initialReleaseDate;
            vestingPeriod[1] = tokensPerRelease;
            // 3 months later release another batch of tokens
            tokensReleased  += tokensPerRelease;
            vestingPeriod[2] = initialReleaseDate + (releaseFrequency * 1);
            vestingPeriod[3] = tokensPerRelease;
            // 3 months later release another batch of tokens
            tokensReleased  += tokensPerRelease;
            vestingPeriod[4] = initialReleaseDate + (releaseFrequency * 2);
            vestingPeriod[5] = tokensPerRelease;
            // 3 months later release another batch of tokens
            tokensReleased  += tokensPerRelease;
            vestingPeriod[6] = initialReleaseDate + (releaseFrequency * 3);
            vestingPeriod[7] = tokensPerRelease;
            // 3 months later release another batch of tokens
            tokensReleased  += tokensPerRelease;
            vestingPeriod[8] = initialReleaseDate + (releaseFrequency * 4);
            vestingPeriod[9] = tokensPerRelease;
            // 3 months later release another batch of tokens
            tokensReleased   += tokensPerRelease;
            vestingPeriod[10] = initialReleaseDate + (releaseFrequency * 5);
            vestingPeriod[11] = tokensPerRelease;
            // 3 months later release the last of the tokens
            vestingPeriod[12] = initialReleaseDate + (releaseFrequency * 6);
            vestingPeriod[13] = individualAllocation - tokensReleased;

            /*
             * Runtime.Notify("VestingSchedule", Helpers.SerializeArray(vestingPeriod));
             * a serialised copy of this array ends up with values such as (dates subject to change):
             *  0e
             *  04 292cf05b          5bf02c29            1542466601         Saturday, November 17, 2018 2:56:41 PM
             *  07 6ddb810adb0301    0103db0a81db6d      285714285714285
             *  04 0979685c	         5c687909            1550350601         Saturday, February 16, 2019 8:56:41 PM
             *  07 dab60315b60702    0207b61503b6da      571428571428570
             *  04 e9c5e05c          5ce0c5e9            1558234601         Sunday, May 19, 2019 2:56:41 AM
             *  07 4792851f910b03    030b911f859247      857142857142855
             *  04 c912595d          5d5912c9            1566118601         Sunday, August 18, 2019 8:56:41 AM
             *  07 b46d072a6c0f04    040f6c2a076db4      1142857142857140
             *  04 a95fd15d          5dd15fa9            1574002601         Sunday, November 17, 2019 2:56:41 PM
             *  07 21498934471305    05134734894921      1428571428571425
             *  04 89ac495e          5e49ac89            1581886601         Sunday, February 16, 2020 8:56:41 PM
             *  07 8e240b3f221706    0617223f0b248e      1714285714285710
             *  04 69f9c15e          5ec1f969            1589770601         Monday, May 18, 2020 2:56:41 AM
             *  07 00008d49fd1a07    071afd498d0000      2000000000000000
             */
            return(vestingPeriod);
        }
Example #15
0
        /// <summary>
        /// NEP5: Transfer tokens from one account to another
        /// </summary>
        /// <param name="from">sender address</param>
        /// <param name="to">recipient address</param>
        /// <param name="amount">number of tokens to transfer</param>
        /// <param name="caller"></param>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static bool Transfer(byte[] from, byte[] to, BigInteger amount, byte[] caller, byte[] entry)
        {
            if (Helpers.GetBlockTimestamp() < ICOTemplate.PublicSaleEndTime())
            {
                Runtime.Log("Transfer() not available before ICOTemplate.PublicSaleEndTime()");
                return(false);
            }

            if (caller != entry && !Helpers.IsContractWhitelistedTransferFrom(caller))
            {
                from = caller;
            }

            if (from.Length != 20 || to.Length != 20)
            {
                Runtime.Log("Transfer() (from|to).Length != 20");
                return(false);
            }

            if (amount < 0)
            {
                Runtime.Log("Transfer() invalid transfer amount must be >= 0");
                throw new Exception();
            }

            BigInteger fromBalance = BalanceOf(from);                   // retrieve balance of originating account

            if (fromBalance < amount)
            {
                Runtime.Log("Transfer() fromBalance < transferValue");
                // don't transfer if funds not available
                return(false);
            }

            if (amount == 0 || from == to)
            {
                // don't accept a meaningless value
                Runtime.Log("Transfer() empty transfer amount or from==to");
                transfer(from, to, amount);
                return(true);    // as per nep5 standard - return true when amount is 0 or from == to
            }

            if (!Runtime.CheckWitness(from))
            {
                // ensure transaction is signed properly by the owner of the tokens
                Runtime.Log("Transfer() CheckWitness failed");
                return(false);
            }

            BigInteger recipientBalance = BalanceOf(to);
            BigInteger recipientAmountSubjectToVesting = TokenSale.SubjectToVestingPeriod(to);
            BigInteger senderAmountSubjectToVesting    = TokenSale.SubjectToVestingPeriod(from);

            BigInteger newBalance = fromBalance - amount;

            Helpers.SetBalanceOf(from, newBalance + senderAmountSubjectToVesting);                  // remove balance from originating account
            Helpers.SetBalanceOf(to, recipientBalance + recipientAmountSubjectToVesting + amount);  // set new balance for destination account

            transfer(from, to, amount);
            return(true);
        }
Example #16
0
        /// <summary>
        /// transfer an amount from the "from" account to the "to" acount if the originator has been approved to transfer the requested amount.
        /// </summary>
        /// <param name="originator"></param>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <param name="amount"></param>
        /// <param name="caller"></param>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static bool TransferFrom(byte[] originator, byte[] from, byte[] to, BigInteger amount, byte[] caller, byte[] entry)
        {
            if (Helpers.GetBlockTimestamp() < ICOTemplate.PublicSaleEndTime())
            {
                Runtime.Log("TransferFrom() not available before ICOTemplate.PublicSaleEndTime()");
                return(false);
            }

            if (caller != entry && !Helpers.IsContractWhitelistedTransferFrom(caller))
            {
                originator = caller;
            }

            if (originator.Length != 20 || from.Length != 20 || to.Length != 20)
            {
                Runtime.Log("TransferFrom() (originator|from|to).Length != 20");
                return(false);
            }

            if (amount < 0)
            {
                Runtime.Log("TransferFrom() invalid transfer amount must be >= 0");
                throw new Exception();
            }

            BigInteger approvedTransferAmount = Allowance(from, to);    // how many tokens is this address authorised to transfer
            BigInteger fromBalance            = BalanceOf(from);        // retrieve balance of authorised account

            if (approvedTransferAmount < amount || fromBalance < amount)
            {
                // don't transfer if funds not available
                Runtime.Notify("TransferFrom() (authorisedAmount|fromBalance) < transferValue", approvedTransferAmount, fromBalance, amount);
                return(false);
            }

            if (amount == 0 || from == to)
            {
                // don't accept a meaningless value
                Runtime.Log("TransferFrom() empty transfer amount or from==to");
                transfer(from, to, amount);
                return(true);    // as per nep5 standard - return true when amount is 0 or from == to
            }

            if (!Runtime.CheckWitness(originator))
            {
                // ensure transaction is signed properly by the request originator
                Runtime.Log("TransferFrom() CheckWitness failed");
                return(false);
            }

            BigInteger recipientBalance = BalanceOf(to);
            BigInteger recipientAmountSubjectToVesting = TokenSale.SubjectToVestingPeriod(to);
            BigInteger senderAmountSubjectToVesting    = TokenSale.SubjectToVestingPeriod(from);

            BigInteger newBalance = fromBalance - amount;

            Helpers.SetBalanceOf(from, newBalance + senderAmountSubjectToVesting);                  // remove balance from originating account
            Helpers.SetBalanceOf(to, recipientBalance + recipientAmountSubjectToVesting + amount);  // set new balance for destination account
            Helpers.SetAllowanceAmount(from.Concat(originator), approvedTransferAmount - amount);   // deduct transferred amount from allowance

            transfer(from, to, amount);
            return(true);
        }
Example #17
0
        /// <summary>
        /// retrieve information for the received transaction
        /// </summary>
        /// <returns>object[] {
        /// (Transaction)tx, (byte[])sender, (byte)receiver, ulong receivedNEO, ulong receivedGAS,
        /// (BigInteger)whiteListGroupNumber, (BigInteger)crowdsaleAvailableAmount, (BigInteger)groupMaximumContribution
        /// (BigInteger)totalTokensPurchased, (BigInteger)neoRemainingAfterPurchase, (BigInteger)gasRemainingAfterPurchase
        /// (BigInteger)totalContributionBalance
        /// }
        /// </returns>
        public static object[] GetTransactionAndSaleData()
        {
            Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;

            TransactionOutput[] inputs    = tx.GetReferences();
            TransactionOutput   reference = inputs[0];

            TransactionOutput[] outputs = tx.GetOutputs();
            byte[] sender      = reference.ScriptHash;
            byte[] receiver    = ExecutionEngine.ExecutingScriptHash;
            ulong  receivedNEO = 0;
            ulong  receivedGAS = 0;

            foreach (var input in inputs)
            {
                // ensure that the provided inputs are valid
                if (input.ScriptHash == receiver)
                {
                    throw new System.Exception();
                }
            }

            foreach (TransactionOutput output in outputs)
            {
                if (output.ScriptHash == receiver)
                {
                    // only add funds to total received value if receiver is the recipient of the output
                    ulong receivedValue = (ulong)output.Value;
                    Runtime.Notify("GetTransactionData() Received Deposit type", receiver, reference.AssetId);
                    if (reference.AssetId == NEP5.NEO)
                    {
                        receivedNEO += receivedValue;
                    }
                    else if (reference.AssetId == NEP5.GAS)
                    {
                        receivedGAS += receivedValue;
                    }
                }
            }

            BigInteger whiteListGroupNumber     = KYC.GetWhitelistGroupNumber(sender);
            BigInteger crowdsaleAvailableAmount = NEP5.CrowdsaleAvailableAmount();
            BigInteger groupMaximumContribution = KYC.GetGroupMaxContribution(whiteListGroupNumber) * NEP5.factor;

            BigInteger totalTokensPurchased      = 0;
            BigInteger neoRemainingAfterPurchase = 0;
            BigInteger gasRemainingAfterPurchase = 0;
            BigInteger runningCrowdsaleAmount    = crowdsaleAvailableAmount;

            if (ICOTemplate.ICOAllowsNEO() && receivedNEO > 0)
            {
                BigInteger neoTokenValue = receivedNEO * ICOTemplate.ICONeoToTokenExchangeRate();
                if (neoTokenValue > runningCrowdsaleAmount)
                {
                    // the user is trying to purchase more tokens than are available
                    // figure out how much NOS can be purchased without exceeding the cap
                    neoRemainingAfterPurchase = (neoTokenValue - runningCrowdsaleAmount) / (ICOTemplate.ICONeoToTokenExchangeRate());
                    totalTokensPurchased      = runningCrowdsaleAmount;
                }
                else
                {
                    // there is enough NOS left for this purchase to complete
                    totalTokensPurchased = neoTokenValue;
                }
                // ensure amountAvailable now reflects number of tokens purchased with NEO
                runningCrowdsaleAmount -= totalTokensPurchased;
            }

            if (ICOTemplate.ICOAllowsGAS() && receivedGAS > 0)
            {
                BigInteger gasTokenValue = receivedGAS * ICOTemplate.ICOGasToTokenExchangeRate();
                if (gasTokenValue > runningCrowdsaleAmount)
                {
                    // the user is trying to purchase more tokens than are available
                    // figure out how much NOS can be purchased without exceeding the cap
                    gasRemainingAfterPurchase = (gasTokenValue - runningCrowdsaleAmount) / (ICOTemplate.ICOGasToTokenExchangeRate());
                    totalTokensPurchased      = totalTokensPurchased + runningCrowdsaleAmount;
                }
                else
                {
                    totalTokensPurchased = totalTokensPurchased + gasTokenValue;
                }
            }

            BigInteger totalContributionBalance = BalanceOfSaleContribution(sender) + totalTokensPurchased;

            return(new object[] {
                tx,                             // neo transaction object
                sender,                         // who initiated the transfer
                receiver,                       // who the assets were sent to
                receivedNEO,                    // how many neo were transferred
                receivedGAS,                    // how many gas were transferred
                whiteListGroupNumber,           // what whitelist group is the sender in
                crowdsaleAvailableAmount,       // how many tokens are left to be purchased
                groupMaximumContribution,       // how many tokens can members of this whitelist group purchase
                totalTokensPurchased,           // the total number of tokens purchased in this transaction
                neoRemainingAfterPurchase,      // how much neo is left after purchase of tokens
                gasRemainingAfterPurchase,      // how much gas is left after purchase of tokens
                totalContributionBalance        // the total amount of tokens sender has purchased during public sale
            });
        }
Example #18
0
        public static object HandleNEP5Operation(string operation, object[] args, byte[] caller, byte[] entry)
        {
            //{ "name", "symbol", "decimals", "totalSupply", "balanceOf", "transfer", "transferFrom", "approve", "allowance" };
            if (operation == "name")
            {
                // the name of the token
                return(ICOTemplate.TokenName());
            }

            if (operation == "symbol")
            {
                // the symbol of the token
                return(ICOTemplate.TokenSymbol());
            }

            if (operation == "decimals")
            {
                // decimals to determine fractions of tokens
                return(TokenDecimals());
            }

            if (operation == "totalSupply")
            {
                // the total number of tokens minted
                return(TotalSupply());
            }

            if (operation == "balanceOf")
            {
                // retreive the balance of an address
                if (!Helpers.RequireArgumentLength(args, 1))
                {
                    // BalanceOf() requires at least 1 argument - the address to check the balance of
                    return(false);
                }

                return(BalanceOf((byte[])args[0]));
            }

            if (operation == "transfer")
            {
                // transfer tokens from one address to another
                if (!Helpers.RequireArgumentLength(args, 3))
                {
                    // Transfer() requires 3 arguments: from, to, amount
                    return(false);
                }

                return(Transfer((byte[])args[0], (byte[])args[1], (BigInteger)args[2], caller, entry));
            }


            if (operation == "transferFrom")
            {
                // transfer tokens from one address to another
                if (!Helpers.RequireArgumentLength(args, 4))
                {
                    // TransferFrom() requires 4 arguments: spender, from, to, amount
                    return(false);
                }

                return(TransferFrom((byte[])args[0], (byte[])args[1], (byte[])args[2], (BigInteger)args[3], caller, entry));
            }

            if (operation == "approve")
            {
                // approve a third party to transfer tokens from one address to another
                if (!Helpers.RequireArgumentLength(args, 3))
                {
                    // Approve() requires 3 arguments: from, spender, amount
                    return(false);
                }

                return(Approve((byte[])args[0], (byte[])args[1], (BigInteger)args[2], caller, entry));
            }

            if (operation == "allowance")
            {
                // retreive the authorised balance of an address
                if (!Helpers.RequireArgumentLength(args, 2))
                {
                    // Allowance() requires 2 arguments: from, to
                    return(false);
                }

                return(Allowance((byte[])args[0], (byte[])args[1]));
            }

            // check how many tokens left for purchase
            if (operation == "crowdsale_available_amount")
            {
                return(CrowdsaleAvailableAmount());
            }

            if (operation == "mintTokens")
            {
                return(TokenSale.MintTokens());
            }
            return(false);
        }
        /// <summary>
        /// set a vesting schedule, as defined in the whitepaper, for tokens purchased during the presale
        /// </summary>
        /// <param name="address"></param>
        /// <param name="tokenBalance"></param>
        /// <returns></returns>
        public static bool SetVestingPeriodForAddress(byte[] address, string allocationType, BigInteger tokensPurchased)
        {
            if (allocationType != "incentive" && allocationType != "privateSale" && allocationType != "company")
            {
                return(false);
            }

            if (address.Length != 20)
            {
                return(false);
            }

            BigInteger currentAvailableBalance = 0;        // how many tokens will be immediately available to the owner

            uint       contractInitTime   = Helpers.GetContractInitTime();
            uint       currentTimestamp   = Helpers.GetBlockTimestamp();
            StorageMap vestingData        = Storage.CurrentContext.CreateMap(StorageKeys.VestedTokenPrefix());
            uint       initialReleaseDate = 0;
            uint       releaseFrequency   = 0;

            object[] vestingObj = new object[0];

            if (allocationType == "incentive")
            {
                vestingObj         = ICOTemplate.VestingIncentive();
                initialReleaseDate = (uint)vestingObj[0] + contractInitTime;
                releaseFrequency   = (uint)vestingObj[1];
            }
            else if (allocationType == "privateSale")
            {
                vestingObj         = ICOTemplate.VestingPrivateSale();
                initialReleaseDate = contractInitTime;
                releaseFrequency   = (uint)vestingObj[0];
            }
            else if (allocationType == "company")
            {
                vestingObj         = ICOTemplate.VestingCompany();
                initialReleaseDate = (uint)vestingObj[0] + contractInitTime;
                releaseFrequency   = (uint)vestingObj[0];
            }

            object[] releasePeriod = new object[4];

            releasePeriod[0] = initialReleaseDate;
            releasePeriod[1] = initialReleaseDate + releaseFrequency;
            releasePeriod[2] = initialReleaseDate + (releaseFrequency * 2);
            releasePeriod[3] = initialReleaseDate + (releaseFrequency * 3);

            // calculate how much should be released
            BigInteger releaseAmount = tokensPurchased * ICOTemplate.DistributionPercentage() / 100;

            object[] lockoutTimes = new object[] { releasePeriod[0], releaseAmount, releasePeriod[1], releaseAmount, releasePeriod[2], releaseAmount, releasePeriod[3], releaseAmount };
            vestingData.Put(address, lockoutTimes.Serialize());

            // ensure the total amount purchased is saved
            Helpers.SetBalanceOf(address, tokensPurchased);
            Helpers.SetBalanceOfVestedAmount(address, tokensPurchased, allocationType);
            transfer(null, address, tokensPurchased);

            return(true);
        }
Example #20
0
        /// <summary>
        /// allow allocation of presale purchases by contract administrator. this allows the moonlight team to allocate the 25% of LX tokens sold in the private presale.
        /// as we accepted ETH in addition to NEO&GAS, using a mintTokens method here is not practical.
        /// 1. this method will not allow the presale allocation to exceed the defined amount
        /// 2. this method is permanently disabled once the method `LockPresaleAllocation` has been called.
        /// 3. the state of the `LockPresaleAllocation` can be determined by the public using the method `IsPresaleAllocationLocked` (returns timestamp that lock was put in place)
        /// </summary>
        /// <param name="address"></param>
        /// <param name="amountPurchased"></param>
        /// <returns></returns>
        public static bool AllocatePresalePurchase(byte[] address, BigInteger amountPurchased)
        {
            bool presaleLocked = Storage.Get(Storage.CurrentContext, StorageKeys.PresaleAllocationLocked()).AsBigInteger() > 0;

            if (presaleLocked)
            {
                Runtime.Notify("AllocatePresalePurchase() presaleLocked, can't allocate");
                return(false);
            }

            BigInteger presaleAllocationMaxValue = ((ICOTemplate.TokenMaxSupply * (BigInteger)ICOTemplate.PresaleAllocationPercentage()) / 100) * NEP5.factor;
            BigInteger presaleAllocatedValue     = Storage.Get(Storage.CurrentContext, StorageKeys.PresaleAllocatedValue()).AsBigInteger();

            if ((presaleAllocatedValue + amountPurchased) > presaleAllocationMaxValue)
            {
                // this purchase will exceed the presale cap.. dont allow
                Runtime.Notify("AllocatePresalePurchase() purchase will exceed presale max allocation");
                return(false);
            }

            TokenSale.SetVestingPeriodForAddress(address, amountPurchased);
            Storage.Put(Storage.CurrentContext, StorageKeys.PresaleAllocatedValue(), presaleAllocatedValue + amountPurchased);
            Runtime.Notify("AllocatePresalePurchase() tokens allocated", address, amountPurchased);

            return(true);
        }