private static bool AddOffer(object[] args, byte[] sender)
        {
            object[] notifyArgs;
            string   firstNotifyArg = "addOfferInitError";

            Offer offer = new Offer();

            offer.Creator      = sender;
            offer.AssetIdToBuy = (byte[])args[0];
            BigInteger quantityToBuy = (BigInteger)args[1];

            offer.QuantityToBuy = quantityToBuy;

            offer.AssetIdToSell = (byte[])args[2];
            BigInteger quantityToSell = (BigInteger)args[3];

            offer.QuantityToSell = quantityToSell;
            offer.Nonce          = (BigInteger)args[4];

            if (offer.AssetIdToBuy.Length != 32 && offer.AssetIdToBuy.Length != 20 ||
                offer.AssetIdToSell.Length != 32 && offer.AssetIdToSell.Length != 20)
            {
                notifyArgs = new object[] { null, "Invalid Asset Length", sender, offer.AssetIdToBuy, offer.AssetIdToSell };
                goto AddOfferErrorNotify;
            }

            if (quantityToBuy <= 0 || quantityToSell <= 0)
            {
                notifyArgs = new object[] { null, "No negative quantities allowed", sender, quantityToBuy, quantityToSell };
                goto AddOfferErrorNotify;
            }

            byte[] offerData = offer.Serialize();

            byte[] offerId  = Hash256(offerData);
            byte[] offerKey = PREFIX_OFFERS.Concat(offerId);

            byte[] existingOfferData = Storage.Get(Storage.CurrentContext, offerKey);
            if (existingOfferData.Length > 0)
            {
                notifyArgs = new object[] { null, "Offer already exists", sender, offerId };
                goto AddOfferErrorNotify;
            }

            firstNotifyArg = "addOfferError";

            var existingWhitelistedUserIdentity = Storage.Get(Storage.CurrentContext, PREFIX_WHITELIST.Concat(sender));

            if (existingWhitelistedUserIdentity.Length == 0)
            {
                notifyArgs = new object[] { null, "Not whitelisted", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell };
                goto AddOfferErrorNotify;
            }

            //validate this is a market we currently support and the order size is enough
            Market offerMarket = GetMarket(offer.AssetIdToBuy, offer.AssetIdToSell);

            if (offerMarket.QuoteAssetId.Length == 0)
            {
                notifyArgs = new object[] { null, "Invalid market", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell };
                goto AddOfferErrorNotify;
            }

            BigInteger minimumTickSize = offerMarket.MinimumTickSize;

            if (offer.AssetIdToBuy == offerMarket.QuoteAssetId)
            {
                if (quantityToBuy < offerMarket.MinimumSize)
                {
                    notifyArgs = new object[] { null, "Insufficient order size", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell, quantityToBuy, minimumTickSize };
                    goto AddOfferErrorNotify;
                }

                BigInteger unitPrice = (quantityToSell * ONE) / quantityToBuy;
                if ((unitPrice / minimumTickSize) * minimumTickSize != unitPrice)
                {
                    notifyArgs = new object[] { null, "Invalid tick size", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell, unitPrice, minimumTickSize };
                    goto AddOfferErrorNotify;
                }
            }
            else
            {
                if (quantityToSell < offerMarket.MinimumSize)
                {
                    notifyArgs = new object[] { null, "Insufficient order size", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell, quantityToSell, minimumTickSize };
                    goto AddOfferErrorNotify;
                }

                BigInteger unitPrice = (quantityToBuy * ONE) / quantityToSell;
                if ((unitPrice / minimumTickSize) * minimumTickSize != unitPrice)
                {
                    notifyArgs = new object[] { null, "Invalid tick size", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell, unitPrice, minimumTickSize };
                    goto AddOfferErrorNotify;
                }
            }

            bool reduced = ReduceBalanceOf(offer.AssetIdToSell, sender, quantityToSell);

            if (reduced == false)
            {
                notifyArgs = new object[] { null, "Unable to reserve asset", sender, offerId, offer.AssetIdToBuy, offer.AssetIdToSell, quantityToSell };
                goto AddOfferErrorNotify;
            }

            Storage.Put(Storage.CurrentContext, offerKey, offerData);
            // Runtime.Notify("PostOffer", sender, offerId, offerData);
            Runtime.Notify("offerCreated", offerId, offer.Creator, offer.AssetIdToBuy, quantityToBuy, offer.AssetIdToSell, quantityToSell);

            return(true);

AddOfferErrorNotify:
            notifyArgs[0] = firstNotifyArg;
            Runtime.Notify(notifyArgs);
            return(false);
        }
        private static bool OnTokenTransfer(byte[] assetId, object[] args)
        {
            if (args.Length < 3)
            {
                return(false);
            }
            object[] notifyArgs;

            byte[]     sender   = (byte[])args[0];
            byte[]     to       = (byte[])args[1];
            BigInteger quantity = (BigInteger)args[2];

            if (to != ExecutionEngine.ExecutingScriptHash)
            {
                notifyArgs = new object[]
                { null, "Transfer not sent to us", sender, assetId, to, ExecutionEngine.ExecutingScriptHash };
                goto OnTokenTransferErrorNotify;
            }

            if (quantity <= 0)
            {
                notifyArgs = new object[]
                { null, "quantity <= 0", sender, assetId, quantity };
                goto OnTokenTransferErrorNotify;
            }

            var existingWhitelistedUserIdentity = Storage.Get(Storage.CurrentContext, PREFIX_WHITELIST.Concat(sender));

            if (existingWhitelistedUserIdentity.Length == 0)
            {
                notifyArgs = new object[]
                { null, "not whitelisted", sender, assetId, quantity };
                goto OnTokenTransferErrorNotify;
            }

            // AssetId must be the asset of the calling script hash

            IncreaseBalanceOf(assetId, sender, quantity);

            AdjustTotalUserDexBalance(assetId, quantity);

            Runtime.Notify("deposit", sender, assetId, quantity);
            return(true);

OnTokenTransferErrorNotify:
            notifyArgs[0] = "tokenTranferError";
            Runtime.Notify(notifyArgs);
            return(false);
        }
Example #3
0
        /// <summary>
        /// Main method of a contract. ParameterList: 0710, ReturnType: 05
        /// </summary>
        /// <param name="operation">Method to invoke</param>
        /// <param name="args">Method parameters</param>
        /// <returns>Method's return value or false if operation is invalid</returns>
        public static object Main(string operation, params object[] args)
        {
            if (Runtime.Trigger == TriggerType.Application)
            {
                // This is here because it is the most GAS intensive operation, moved to top to avoid all the other comparisons
                if (operation == "acceptOffer")
                {
                    return(AcceptOffer(args));
                }
                if (operation == "withdraw")
                {
                    return(ExecuteWithdraw());
                }

                if (operation == "getBalance")
                {
                    return(GetBalanceOfForWithdrawal((byte[])args[0], (byte[])args[1]));
                }
                if (operation == "getContributed")
                {
                    return(GetContributed((byte[])args[0]));
                }
                if (operation == "getAvailableToClaim")
                {
                    return(GetAvailableToClaim((byte[])args[0]));
                }
                if (operation == "getAphConversionRate")
                {
                    return(GetAssetToAphConversionRate((byte[])args[0]));
                }

                byte[]      unverifiedSender = EMPTY;
                Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;

                // first check to see if the transaction has a valid sender attribute (and verify that address is also a witness)
                TransactionAttribute[] attributes = tx.GetAttributes();

                foreach (TransactionAttribute attr in attributes)
                {
                    if (attr.Usage == SENDER_ATTRIBUTE_USAGE && attr.Data != ExecutionEngine.ExecutingScriptHash)
                    {
                        unverifiedSender = attr.Data;
                        break;
                    }
                }

                if (operation == "addOffer")
                {
                    if (args.Length != 5)
                    {
                        return(false);
                    }

                    byte[] assetIdToSell = (byte[])args[2];
                    // We count on ReduceBalanceOf verifying the sender.
                    // Optimize gas usage for adding an offer that must pull a NEP5 asset
                    if (assetIdToSell.Length == 20)
                    {
                        return(AddOffer(args, unverifiedSender));
                    }
                }
                else if (operation == "deposit")
                {
                    if (args.Length != 2)
                    {
                        return(false);
                    }

                    byte[] assetId = (byte[])args[0];
                    var    existingWhitelistedUserIdentity = Storage.Get(Storage.CurrentContext,
                                                                         PREFIX_WHITELIST.Concat(unverifiedSender));
                    if (existingWhitelistedUserIdentity.Length == 0)
                    {
                        Runtime.Notify("depositError", "not whitelisted", unverifiedSender, assetId,
                                       (BigInteger)args[1]);
                        return(false);
                    }

                    if (assetId.Length == 20)
                    {
                        // Optimize gas usage for NEP5 deposit. We count on PullNep5 to check the witness.
                        BigInteger quantity = (BigInteger)args[1];
                        return(DepositNep5(assetId, quantity, unverifiedSender));
                    }
                    if (assetId.Length != 32)
                    {
                        return(false);
                    }
                    // for assetId.Length == 32, deposit will be handled after CaptureSystemAssetsSent
                }

                // ExecutionEngine.CallingScriptHash needs to be captured here in order to get the script hash of the
                // NEP5 token which is dynamically calling us
                if (operation == "onTokenTransfer")
                {
                    return(OnTokenTransfer(ExecutionEngine.CallingScriptHash, args));
                }

                byte[] sender;

                // byte[] sender = GetSender(); // GetSender() now inlined in the below block, since occurred only once.
                {
                    sender = EMPTY;
                    if (attributes.Length > MAX_ATTRIBUTES)
                    {
                        // Prevent execution with too many attributes, may be a potential attack vector to cause the contract to run out of GAS
                        return(false);
                    }

                    if (Runtime.CheckWitness(unverifiedSender))
                    {
                        sender = unverifiedSender;
                        // Runtime.Notify("sender not a witness", sender);
                        goto CaptureSystemAssetsSent; // return sender;
                    }


                    foreach (TransactionAttribute a in attributes)
                    {
                        if (a.Usage == SENDER_ATTRIBUTE_USAGE && a.Data != unverifiedSender)
                        {
                            if (Runtime.CheckWitness(a.Data) == false)
                            {
                                // Runtime.Notify("sender not a witness", sender);
                                continue;
                            }
                            sender = a.Data;

                            //Runtime.Notify("GetSender() sender", sender);
                            goto CaptureSystemAssetsSent; // return sender;
                        }
                    }

                    //otherwise get the sender from an input reference (they sent a system asset along with the invocation)
                    TransactionOutput[] references = tx.GetReferences();
                    if (references.Length > MAX_REFERENCES)
                    {
                        //prevent execution with too many references, may be a potential attack vector to cause the contract to run out of GAS
                        goto CaptureSystemAssetsSent; // return EMPTY;
                    }

                    foreach (TransactionOutput r in references)
                    {
                        //Runtime.Notify("GetSender() sender", references[0].ScriptHash);
                        if (r.ScriptHash != ExecutionEngine.ExecutingScriptHash)
                        {
                            sender = r.ScriptHash;
                            goto CaptureSystemAssetsSent; // return sender;
                        }
                    }

                    Runtime.Notify("no sender");
                    goto NoSenderRequiredOperations; // return;
                }

CaptureSystemAssetsSent: // inline due to only a single call
                // Only deposit and addOffer can accept NEO or GAS funds and credit balances.
                if (operation != "deposit" && operation != "addOffer")
                {
                    goto RunOperation;
                }

                //private static void CaptureSystemAssetsSent(byte[] sender)
                long neoSentToContract = 0;
                long gasSentToContract = 0;
                {
                    TransactionOutput[] outputs = tx.GetOutputs();

                    if (outputs.Length == 0)
                    {
                        goto AddOfferOrDeposit; // return;
                    }

                    foreach (TransactionOutput o in outputs)
                    {
                        if (o.ScriptHash != ExecutionEngine.ExecutingScriptHash)
                        {
                            continue;
                        }

                        if (o.AssetId == NEO)
                        {
                            neoSentToContract += o.Value;
                        }
                        if (o.AssetId == GAS)
                        {
                            gasSentToContract += o.Value;
                        }
                    }

                    if (neoSentToContract > 0 || gasSentToContract > 0)
                    {
                        TransactionOutput[] inputReferences = tx.GetReferences();
                        if (neoSentToContract > 0)
                        {
                            foreach (TransactionOutput r in inputReferences)
                            {
                                if (r.ScriptHash != sender && r.AssetId == NEO)
                                {
                                    //this wasn't sent from the sender
                                    neoSentToContract -= r.Value;
                                }
                            }

                            if (neoSentToContract > 0)
                            {
                                //Runtime.Notify("CaptureSystemAssetsSent() NEO sent to contract.", sender, neoSentToContract);
                                IncreaseBalanceOf(NEO, sender, neoSentToContract);
                                // Add to user balances total owned by the exchange
                                AdjustTotalUserDexBalance(NEO, neoSentToContract);
                            }
                        }
                        if (gasSentToContract > 0)
                        {
                            foreach (TransactionOutput r in inputReferences)
                            {
                                if (r.ScriptHash != sender && r.AssetId == GAS)
                                {
                                    //this wasn't sent from the sender
                                    gasSentToContract -= r.Value;
                                }
                            }

                            if (gasSentToContract > 0)
                            {
                                //Runtime.Notify("CaptureSystemAssetsSent() GAS sent to contract.", sender, gasSentToContract);
                                IncreaseBalanceOf(GAS, sender, gasSentToContract);

                                // Add to user balances total owned by the exchange
                                AdjustTotalUserDexBalance(GAS, gasSentToContract);
                            }
                        }
                    }
                }

AddOfferOrDeposit:
                if (operation == "addOffer")
                {
                    return(AddOffer(args, sender));
                }
                if (operation == "deposit")
                {
                    // already added to user's balance in CaptureSystemAssetsSent() method
                    byte[] assetId = (byte[])args[0];

                    long amountDeposited;
                    if (assetId == NEO)
                    {
                        amountDeposited = neoSentToContract;
                    }
                    else if (assetId == GAS)
                    {
                        amountDeposited = gasSentToContract;
                    }
                    else
                    {
                        return(false);
                    }

                    Runtime.Notify("deposit", sender, assetId, amountDeposited);
                    return(true);
                }

RunOperation:
                // Guaranteed to have sender for these
                if (operation == "commit")
                {
                    return(Commit(args, sender));
                }
                if (operation == "claim")
                {
                    return(Claim(sender));
                }
                if (operation == "compound")
                {
                    return(Compound(sender));
                }
                if (operation == "cancelOffer")
                {
                    return(CancelOffer(args, sender));
                }
                if (operation == "send")
                {
                    if (VerifyOwner() == false && VerifyManager() == false)
                    {
                        return(false);
                    }
                    if (args.Length != 3)
                    {
                        return(false);
                    }
                    byte[] assetId = (byte[])args[0];
                    if (assetId.Length != 20 && assetId.Length != 32)
                    {
                        return(false);
                    }
                    BigInteger quantity = (BigInteger)args[1];
                    if (quantity <= 0)
                    {
                        return(false);
                    }
                    byte[] toAddress = (byte[])args[2];
                    if (toAddress.Length != 20)
                    {
                        return(false);
                    }
                    var senderBalance = GetBalanceOfForWithdrawal(assetId, sender);
                    if (senderBalance < quantity)
                    {
                        return(false);
                    }
                    senderBalance -= quantity;
                    SetBalanceOfForWithdrawal(assetId, sender, senderBalance);
                    var receiverBalance = GetBalanceOfForWithdrawal(assetId, toAddress);
                    receiverBalance += quantity;
                    SetBalanceOfForWithdrawal(assetId, toAddress, receiverBalance);
                    Runtime.Notify("sent", sender, toAddress, quantity);
                    return(true);
                }

NoSenderRequiredOperations:
                bool succeeded = false;
                StorageContext storageContext = Storage.CurrentContext;

                if (operation == "setOwner")
                {
                    succeeded = SetOwner((byte[])args[0]);
                }
                else if (operation == "setManager")
                {
                    succeeded = SetManager((byte[])args[0]);
                }
                else if (operation == "setMarket")
                {
                    succeeded = SetMarket(args);
                }
                else if (operation == "closeMarket")
                {
                    succeeded = CloseMarket(args);
                }
                else if (operation == "setAssetToAphRate")
                {
                    succeeded = SetAssetToAphConversionRate((byte[])args[0], (BigInteger)args[1]);
                }
                else if (operation == "setFeeRedistributionPercentage")
                {
                    succeeded = SetFeeRedistributionPercentage((BigInteger)args[0]);
                }
                else if (operation == "setClaimMinimumBlocks")
                {
                    succeeded = SetClaimMinimumBlocks((BigInteger)args[0]);
                }
                else if (operation == "reclaimOrphanFunds")
                {
                    succeeded = ReclaimOrphanFunds((byte[])args[0]);
                }
                else if (operation == "setAssetSettings")
                {
                    succeeded = SetAssetSettings((byte[])args[0], (byte[])args[1]);
                }
                else if (operation == "addIdentity")
                {
                    if (Runtime.CheckWitness(GetWhitelister()) == false)
                    {
                        return(false);
                    }
                    if (args.Length < 4)
                    {
                        return(false);
                    }

                    byte[] userIdentityHash = (byte[])args[0];
                    if (userIdentityHash.Length != 32)
                    {
                        return(false);
                    }
                    byte[] userInfoHash1 = (byte[])args[1];
                    if (userInfoHash1.Length != 0 && userInfoHash1.Length != 32)
                    {
                        return(false);
                    }
                    byte[] userInfoHash2 = (byte[])args[2];
                    if (userInfoHash2.Length != 0 && userInfoHash2.Length != 32)
                    {
                        return(false);
                    }
                    byte[] userScriptHash = (byte[])args[3];
                    if (userScriptHash.Length != 20)
                    {
                        return(false);
                    }
                    byte[] miscUserInfo;
                    if (args.Length > 4)
                    {
                        miscUserInfo = (byte[])args[4];
                    }
                    else
                    {
                        miscUserInfo = new byte[0];
                    }
                    var userIdentity = new UserIdentity();
                    userIdentity.HashInfo1    = userInfoHash1;
                    userIdentity.HashInfo2    = userInfoHash2;
                    userIdentity.ScriptHash   = userScriptHash;
                    userIdentity.MiscUserInfo = miscUserInfo;

                    var existingIdentity =
                        Storage.Get(storageContext, PREFIX_USERIDENTITY.Concat(userIdentityHash));
                    if (existingIdentity.Length != 0)
                    {
                        var existingUserIdentity = (UserIdentity)existingIdentity.Deserialize();
                        if (existingUserIdentity.ScriptHash != userScriptHash)
                        {
                            // if this identity already exists but associated with a different address
                            // De-whitelist the previously associated address for this identity
                            Storage.Delete(storageContext,
                                           PREFIX_WHITELIST.Concat(existingUserIdentity.ScriptHash));
                        }
                    }

                    var identityData = userIdentity.Serialize();
                    Storage.Put(Storage.CurrentContext, PREFIX_USERIDENTITY.Concat(userIdentityHash), identityData);
                    Runtime.Notify("setIdentity", userIdentityHash, userInfoHash1, userInfoHash2, userScriptHash, miscUserInfo);
                    return(true);
                }
                else if (operation == "whitelistAddress")
                {
                    if (Runtime.CheckWitness(GetWhitelister()) == false &&
                        Runtime.CheckWitness(GetOwner()) == false)
                    {
                        return(false);
                    }
                    if (args.Length != 2)
                    {
                        return(false);
                    }
                    byte[] userScriptHash = (byte[])args[0];
                    if (userScriptHash.Length != 20)
                    {
                        return(false);
                    }
                    byte[] userIdentityHash = (byte[])args[1];
                    if (userIdentityHash.Length != 32)
                    {
                        return(false);
                    }

                    object[] notifyArgs;
                    var      existingIdentity =
                        Storage.Get(storageContext, PREFIX_USERIDENTITY.Concat(userIdentityHash));
                    if (existingIdentity.Length == 0)
                    {
                        notifyArgs = new object[] { null, "missing identity", userIdentityHash };
                        goto WhitelistErrorNotify;
                    }

                    var userIdentity = (UserIdentity)existingIdentity.Deserialize();
                    // If identity passed belongs to a different address.
                    if (userIdentity.ScriptHash != userScriptHash)
                    {
                        notifyArgs = new object[] { null, "address mismatch", userIdentityHash, userScriptHash,
                                                    userIdentity.ScriptHash };
                        goto WhitelistErrorNotify;
                    }

                    // check if existing address already whitelisted
                    var existingWhitelistedUserIdentity =
                        Storage.Get(storageContext, userScriptHash);
                    if (existingWhitelistedUserIdentity.Length != 0)
                    {
                        if (existingWhitelistedUserIdentity != userIdentityHash)
                        {
                            // fail since this address is already whitelisted with a different identity
                            // The identity currently owning this address should first be switched to a different
                            // address, which will de-whitelist this address and allow it to be associated with a
                            // different identity.
                            notifyArgs = new object[] { null, "already whitelisted", userIdentityHash, userScriptHash };
                            goto WhitelistErrorNotify;
                        }
                    }
                    else
                    {
                        Storage.Put(storageContext, PREFIX_WHITELIST.Concat(userScriptHash), userIdentityHash);
                    }

                    Runtime.Notify("whitelisted", userScriptHash, userIdentityHash);
                    return(true);

WhitelistErrorNotify:
                    notifyArgs[0] = "whitelistFail";
                    Runtime.Notify(notifyArgs);
                    return(false);
                }
                else if (operation == "blacklistAddress")
                {
                    if (Runtime.CheckWitness(GetWhitelister()) == false &&
                        Runtime.CheckWitness(GetOwner()) == false)
                    {
                        return(false);
                    }
                    byte[] userScriptHash = (byte[])args[0];
                    if (userScriptHash.Length != 20)
                    {
                        return(false);
                    }
                    // just remove from whitelist
                    Storage.Delete(storageContext, PREFIX_WHITELIST.Concat(userScriptHash));
                    Runtime.Notify("blacklisted", userScriptHash);
                }
                else if (operation == "aphNotify")
                {
                    succeeded = AphNotify(args);
                }
                else if (operation == "setWhitelister")
                {
                    if (VerifyOwner() == false)
                    {
                        // Runtime.Notify("setManagerFail", "No perms", newManager);
                        return(false);
                    }

                    Storage.Put(storageContext, "whitelister", (byte[])args[0]);
                    Runtime.Notify("setWhitelister", (byte[])args[0]);
                    return(true);
                }
                // Don't support contract migrate -- too dangerous if keys fell into wrong hands --
                /* else if (operation == "migrate") return MigrateContract(args); */
                else
                {
                    Runtime.Notify(operation + " - Unknown operation.");
                }

                Runtime.Notify(succeeded ? "Success" : "Failure", operation);

                return(succeeded);
            }

            if (Runtime.Trigger == TriggerType.Verification)
            {
                return(ValidateSignatureRequest());
            }

            if (Runtime.Trigger == TriggerType.VerificationR)
            {
                Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;
                if (tx.Type != InvocationTransactionType)
                {
                    Runtime.Notify("Send must use Invocation TX", tx.Type);
                    return(false);
                }

                if (operation == "acceptOffer" ||
                    operation == "addOffer" ||
                    operation == "withdraw" ||
                    operation == "onTokenTransfer" ||
                    operation == "getBalance" ||
                    operation == "getContributed" ||
                    operation == "getAvailableToClaim" ||
                    operation == "getAphConversionRate")
                {
                    Runtime.Notify("OP can't accept sent funds.", operation);
                    return(false);
                }

                return(true);
            }

            return(false);
        }