Example #1
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 = ICOContract.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 #2
0
        /// <summary>
        /// determine if address is one of the founder keys or the ML project key. if so they are still subject to vesting rules
        /// </summary>
        /// <param name="address"></param>
        /// <returns>bool</returns>
        public static bool IsProjectKey(byte[] address)
        {
            object[] keys = ICOContract.MoonlightFounderKeys();

            if (address == (byte[])keys[0] || address == (byte[])keys[1] || address == (byte[])keys[2] ||
                address == (byte[])keys[3] || address == (byte[])keys[4] || address == ICOContract.MoonlightProjectKey())
            {
                return(true);
            }

            return(false);
        }
Example #3
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(ICOContract.MaximumContributionAmount());
        }
Example #4
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 = ICOContract.ImmediateProjectGrowthAllocation();
            object[] vestedAllocation    = ICOContract.VestedProjectGrowthAllocation();

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

            Helpers.SetBalanceOf(ICOContract.MoonlightProjectKey(), immediateProjectAllocationValue + vestedProjectAllocationValue);
            Helpers.SetBalanceOfVestedAmount(ICOContract.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(ICOContract.MoonlightProjectKey(), vestedTokenPeriod.Serialize());

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

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

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

            Helpers.SetTotalSupply(totalSupply);

            UpdateAdminAddress(ICOContract.InitialAdminAccount);
            EnableDEXWhitelisting(ICOContract.WhitelistDEXListings());
            Runtime.Log("InitSmartContract() contract initialisation complete");
            return(true);
        }
        /// <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 (!ICOContract.UseTokenVestingPeriod())
            {
                return(false);
            }

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

            object[]   vestingOne              = ICOContract.VestingBracketOne();
            object[]   vestingTwo              = ICOContract.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 #6
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 "IsPresaleAllocationLocked":
                // 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.PresaleAllocationLocked()));

            case "supportedStandards":
                // support NEP-10 by responding to supportedStandards
                // https://github.com/neo-project/proposals/blob/master/nep-10.mediawiki
                return(ICOContract.SupportedStandards());
            }
            return(false);
        }
Example #7
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            = ICOContract.MoonlightFounderKeys();
            BigInteger founderTokenAllocation = ((ICOContract.TokenMaxSupply * (BigInteger)ICOContract.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 = ICOContract.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 #8
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 = ((ICOContract.TokenMaxSupply * (BigInteger)ICOContract.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);
        }
Example #9
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(ICOContract.TokenName());
            }

            if (operation == "symbol")
            {
                // the symbol of the token
                return(ICOContract.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: originator, 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: originator, to, 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);
        }
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[] 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 (ICOContract.ICOAllowsNEO() && receivedNEO > 0)
            {
                BigInteger neoTokenValue = receivedNEO * ICOContract.ICONeoToTokenExchangeRate();
                if (neoTokenValue > runningCrowdsaleAmount)
                {
                    // the user is trying to purchase more tokens than are available
                    // figure out how much LX can be purchased without exceeding the cap
                    neoRemainingAfterPurchase = (neoTokenValue - runningCrowdsaleAmount) / (ICOContract.ICONeoToTokenExchangeRate());
                    totalTokensPurchased      = runningCrowdsaleAmount;
                }
                else
                {
                    // there is enough LX left for this purchase to complete
                    totalTokensPurchased = neoTokenValue;
                }
                // ensure amountAvailable now reflects number of tokens purchased with NEO
                runningCrowdsaleAmount -= totalTokensPurchased;
            }

            if (ICOContract.ICOAllowsGAS() && receivedGAS > 0)
            {
                BigInteger gasTokenValue = receivedGAS * ICOContract.ICOGasToTokenExchangeRate();
                if (gasTokenValue > runningCrowdsaleAmount)
                {
                    // the user is trying to purchase more tokens than are available
                    // figure out how much LX can be purchased without exceeding the cap
                    gasRemainingAfterPurchase = (gasTokenValue - runningCrowdsaleAmount) / (ICOContract.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
            });
        }