Browse Source

Fix PIP-0032 protections and add OperationResume info

PascalCoin 6 years ago
parent
commit
8f82e25682

+ 40 - 28
src/core/UAccounts.pas

@@ -25,7 +25,7 @@ interface
 uses
 uses
   Classes, SysUtils, UConst, UCrypto, SyncObjs, UThread, UBaseTypes,
   Classes, SysUtils, UConst, UCrypto, SyncObjs, UThread, UBaseTypes,
   UPCOrderedLists, UPCDataTypes, UPCSafeBoxRootHash,
   UPCOrderedLists, UPCDataTypes, UPCSafeBoxRootHash,
-  UPCHardcodedRandomHashTable,
+  UPCHardcodedRandomHashTable, UJSONFunctions,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF};
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF};
 
 
 {$I config.inc}
 {$I config.inc}
@@ -148,7 +148,7 @@ Type
     Class function IsAccountForCoinSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class function IsAccountForCoinSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class function IsAccountForAccountSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class function IsAccountForAccountSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class Function IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class Function IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo) : Boolean;
-    Class Function IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; const APayload : TRawBytes) : Boolean;
+    Class Function IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word; const APayload : TRawBytes) : Boolean;
     Class Function IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
     Class Function IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
@@ -1422,7 +1422,7 @@ end;
 
 
 class function TAccountComp.FormatMoney(Money: Int64): String;
 class function TAccountComp.FormatMoney(Money: Int64): String;
 begin
 begin
-  Result := FormatFloat('#,###0.0000',(Money/10000));
+  Result := FormatFloat('#,###0.0000',(Money/10000), TPCJSONData.JSONFormatSettings);
 end;
 end;
 
 
 class function TAccountComp.FormatMoneyDecimal(Money : Int64) : Currency;
 class function TAccountComp.FormatMoneyDecimal(Money : Int64) : Currency;
@@ -1511,13 +1511,18 @@ begin
   Result := IsAccountForSale(AAccountInfo) OR IsAccountForSwap(AAccountInfo);
   Result := IsAccountForSale(AAccountInfo) OR IsAccountForSwap(AAccountInfo);
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; const APayload : TRawBytes): Boolean;
+class function TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word; const APayload : TRawBytes): Boolean;
 var errors : String;
 var errors : String;
 begin
 begin
   Result := False;
   Result := False;
   if Not IsAccountForSaleOrSwap(AAccount.accountInfo) then
   if Not IsAccountForSaleOrSwap(AAccount.accountInfo) then
     exit;
     exit;
 
 
+  if (ACurrentProtocol<CT_PROTOCOL_5) then begin
+    // V4 and below only allows Private sales (No Swaps)
+    if Not (IsAccountForPrivateSale(AAccount.accountInfo)) then Exit;
+  end;
+
   if (AAccount.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
   if (AAccount.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
     if NOT IsValidAccountKey(AAccount.accountInfo.new_publicKey,errors) then
     if NOT IsValidAccountKey(AAccount.accountInfo.new_publicKey,errors) then
       exit;
       exit;
@@ -4189,6 +4194,35 @@ begin
     Exit;
     Exit;
   end;
   end;
 
 
+
+  // Overflow checks:
+  if AAmount > (AAmount + AFee) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 37E7343143614D4C8489FA9963CE8C3C';
+    exit;
+  end;
+  if LPBuyerAccount^.balance < (AAmount + AFee) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: F06169D9A209410AACB1AAD324B7A191';
+    exit;
+  end;
+
+  if LPAccountToBuy^.balance > (LPAccountToBuy^.balance + AAmount) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 390BB1E7241B4A0BA5A2D934E67F39D3';
+    exit;
+  end;
+  if (LPAccountToBuy^.balance + AAmount) < (LPAccountToBuy^.accountInfo.price) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 19D80241DA584D0B93ED30F27110B9A9';
+    exit;
+  end;
+
+  if LPSellerAccount^.balance > (LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 972CA85C6E4E4081ABB767F6D8019421';
+    exit;
+  end;
+
+
+  // NOTE:
+  // At this point, we have checked integrity, cannot check later!
+
   UpdateSeal(LPBuyerAccount_Sealed,AOpID);
   UpdateSeal(LPBuyerAccount_Sealed,AOpID);
   UpdateSeal(LPAccountToBuy_Sealed,AOpID);
   UpdateSeal(LPAccountToBuy_Sealed,AOpID);
   UpdateSeal(LPSellerAccount_Sealed,AOpID);
   UpdateSeal(LPSellerAccount_Sealed,AOpID);
@@ -4214,32 +4248,10 @@ begin
 
 
   // Inc buyer n_operation
   // Inc buyer n_operation
   LPBuyerAccount^.n_operation := ANOperation;
   LPBuyerAccount^.n_operation := ANOperation;
-  // Set new balance values (with overflow checks)
-  if AAmount > (AAmount + AFee) then begin
-    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 37E7343143614D4C8489FA9963CE8C3C';
-    exit;
-  end;
-  if LPBuyerAccount^.balance < (AAmount + AFee) then begin
-    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: F06169D9A209410AACB1AAD324B7A191';
-    exit;
-  end;
+  // Set new balance values
   LPBuyerAccount^.balance := LPBuyerAccount^.balance - (AAmount + AFee);
   LPBuyerAccount^.balance := LPBuyerAccount^.balance - (AAmount + AFee);
-
-  if LPAccountToBuy^.balance > (LPAccountToBuy^.balance + AAmount) then begin
-    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 390BB1E7241B4A0BA5A2D934E67F39D3';
-    exit;
-  end;
-  if (LPAccountToBuy^.balance + AAmount) < (LPAccountToBuy^.accountInfo.price) then begin
-    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 19D80241DA584D0B93ED30F27110B9A9';
-    exit;
-  end;
   LPAccountToBuy^.balance := LPAccountToBuy^.balance + AAmount - LPAccountToBuy^.accountInfo.price;
   LPAccountToBuy^.balance := LPAccountToBuy^.balance + AAmount - LPAccountToBuy^.accountInfo.price;
-
-  if LPSellerAccount^.balance > (LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price) then begin
-    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 972CA85C6E4E4081ABB767F6D8019421';
-    exit;
-  end;
-    LPSellerAccount^.balance := LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price;
+  LPSellerAccount^.balance := LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price;
 
 
   // After buy, account will be unlocked and set to normal state and new account public key changed
   // After buy, account will be unlocked and set to normal state and new account public key changed
   LPAccountToBuy^.accountInfo := CT_AccountInfo_NUL;
   LPAccountToBuy^.accountInfo := CT_AccountInfo_NUL;

+ 48 - 0
src/core/UBlockChain.pas

@@ -3188,6 +3188,7 @@ end;
 
 
 class function TPCOperation.OperationToOperationResume(Block : Cardinal; Operation: TPCOperation; getInfoForAllAccounts : Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume): Boolean;
 class function TPCOperation.OperationToOperationResume(Block : Cardinal; Operation: TPCOperation; getInfoForAllAccounts : Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume): Boolean;
 Var s : String;
 Var s : String;
+  LOpToText : String;
 begin
 begin
   OperationResume := CT_TOperationResume_NUL;
   OperationResume := CT_TOperationResume_NUL;
   OperationResume.Block:=Block;
   OperationResume.Block:=Block;
@@ -3227,6 +3228,53 @@ begin
           OperationResume.Fee := 0;
           OperationResume.Fee := 0;
           Result := true;
           Result := true;
         end else exit;
         end else exit;
+      end else if (TOpTransaction(Operation).Data.opTransactionStyle = transaction_with_auto_atomic_swap) then begin
+        if TOpTransaction(Operation).Data.new_accountkey.EC_OpenSSL_NID=0 then begin
+          // COIN SWAP
+          LOpToText := Format('COIN SWAP %s PASC from %s to %s',[
+            TAccountComp.FormatMoney(TOpTransaction(Operation).Data.AccountPrice),
+            TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.target),
+            TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.SellerAccount)]);
+        end else begin
+          // ACCOUNT SWAP
+          LOpToText := Format('ACCOUNT SWAP %s to new PublicKey %s',[
+            TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.target),
+            TAccountComp.AccountPublicKeyExport(TOpTransaction(Operation).Data.new_accountkey)]);
+        end;
+        if TOpTransaction(Operation).Data.sender=Affected_account_number then begin
+          // The sender of the transaction
+          OperationResume.OpSubtype := CT_OpSubtype_SwapTransactionSender;
+          OperationResume.OperationTxt := Format('Tx-Out %s PASC from %s to %s with %s',
+             [TAccountComp.FormatMoney(TOpTransaction(Operation).Data.amount),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.sender),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.target),
+             LOpToText]);
+          If (TOpTransaction(Operation).Data.sender=TOpTransaction(Operation).Data.SellerAccount) then begin
+            // Valid calc when sender is the same than seller
+            OperationResume.Amount := (Int64(TOpTransaction(Operation).Data.amount) - (TOpTransaction(Operation).Data.AccountPrice)) * (-1);
+          end else OperationResume.Amount := Int64(TOpTransaction(Operation).Data.amount) * (-1);
+          Result := true;
+        end else if TOpTransaction(Operation).Data.target=Affected_account_number then begin
+          OperationResume.OpSubtype := CT_OpSubtype_SwapTransactionTarget;
+          OperationResume.OperationTxt := Format('Tx-In %s PASC from %s to %s with %s',
+             [TAccountComp.FormatMoney(TOpTransaction(Operation).Data.amount),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.sender),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.target),
+             LOpToText]);
+          OperationResume.Amount := Int64(TOpTransaction(Operation).Data.amount) - Int64(TOpTransaction(Operation).Data.AccountPrice);
+          OperationResume.Fee := 0;
+          Result := true;
+        end else if TOpTransaction(Operation).Data.SellerAccount=Affected_account_number then begin
+          OperationResume.OpSubtype := CT_OpSubtype_BuyTransactionSeller;
+          OperationResume.OperationTxt := Format('Tx-In seller receiving %s PASC from Tx between %s to %s with %s',
+             [TAccountComp.FormatMoney(TOpTransaction(Operation).Data.AccountPrice),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.sender),
+             TAccountComp.AccountNumberToAccountTxtNumber(TOpTransaction(Operation).Data.target),
+             LOpToText]);
+          OperationResume.Amount := TOpTransaction(Operation).Data.AccountPrice;
+          OperationResume.Fee := 0;
+          Result := true;
+        end else exit;
       end else begin
       end else begin
         if TOpTransaction(Operation).Data.sender=Affected_account_number then begin
         if TOpTransaction(Operation).Data.sender=Affected_account_number then begin
           OperationResume.OpSubtype := CT_OpSubtype_TransactionSender;
           OperationResume.OpSubtype := CT_OpSubtype_TransactionSender;

+ 3 - 0
src/core/UConst.pas

@@ -167,6 +167,9 @@ Const
   CT_OpSubtype_BuyTransactionBuyer        = 13;
   CT_OpSubtype_BuyTransactionBuyer        = 13;
   CT_OpSubtype_BuyTransactionTarget       = 14;
   CT_OpSubtype_BuyTransactionTarget       = 14;
   CT_OpSubtype_BuyTransactionSeller       = 15;
   CT_OpSubtype_BuyTransactionSeller       = 15;
+  CT_OpSubtype_SwapTransactionSender      = 16;
+  CT_OpSubtype_SwapTransactionTarget      = 17;
+  CT_OpSubtype_SwapTransactionReceiver    = 18;
   CT_OpSubtype_ChangeKey                  = 21;
   CT_OpSubtype_ChangeKey                  = 21;
   CT_OpSubtype_Recover                    = 31;
   CT_OpSubtype_Recover                    = 31;
   CT_OpSubtype_ListAccountForPublicSale   = 41;
   CT_OpSubtype_ListAccountForPublicSale   = 41;

+ 113 - 52
src/core/UOpTransaction.pas

@@ -31,7 +31,12 @@ Uses UCrypto, UBlockChain, Classes, UAccounts, UBaseTypes,
 
 
 Type
 Type
   // Operations Type
   // Operations Type
-  TOpTransactionStyle = (transaction, transaction_with_auto_buy_account, buy_account, atomic_swap, transaction_with_auto_atomic_swap);
+  TOpTransactionStyle = (transaction, transaction_with_auto_buy_account, buy_account, transaction_with_auto_atomic_swap);
+    // transaction = Sinlge standard transaction
+    // transaction_with_auto_buy_account = Single transaction made over an account listed for private sale. For STORING purposes only
+    // buy_account = A Buy account operation
+    // transaction_with_auto_atomic_swap = Single transaction made over an account listed for atomic swap (coin swap or account swap)
+
   TOpTransactionData = Record
   TOpTransactionData = Record
     sender: Cardinal;
     sender: Cardinal;
     n_operation : Cardinal;
     n_operation : Cardinal;
@@ -47,7 +52,6 @@ Type
     AccountPrice : UInt64;
     AccountPrice : UInt64;
     SellerAccount : Cardinal;
     SellerAccount : Cardinal;
     new_accountkey : TAccountKey;
     new_accountkey : TAccountKey;
-
   End;
   End;
 
 
   TOpChangeKeyData = Record
   TOpChangeKeyData = Record
@@ -727,7 +731,7 @@ procedure TOpTransaction.AffectedAccounts(list: TList<Cardinal>);
 begin
 begin
   list.Add(FData.sender);
   list.Add(FData.sender);
   list.Add(FData.target);
   list.Add(FData.target);
-  if (FData.opTransactionStyle in [transaction_with_auto_buy_account, buy_account, atomic_swap, transaction_with_auto_atomic_swap]) then begin
+  if (FData.opTransactionStyle in [transaction_with_auto_buy_account, buy_account, transaction_with_auto_atomic_swap]) then begin
     list.Add(FData.SellerAccount);
     list.Add(FData.SellerAccount);
   end;
   end;
 end;
 end;
@@ -759,8 +763,9 @@ function TOpTransaction.DoOperation(APrevious : TAccountPreviousBlockInfo; ASafe
 Var s_new, t_new : Int64;
 Var s_new, t_new : Int64;
   LTotalAmount : Cardinal;
   LTotalAmount : Cardinal;
   LSender,LTarget,LSeller : TAccount;
   LSender,LTarget,LSeller : TAccount;
-  LRecipientSignable, LIsSwap : Boolean;
+  LRecipientSignable, LIsCoinSwap : Boolean;
   LCurrentBlock, LCurrentProtocol : Integer;
   LCurrentBlock, LCurrentProtocol : Integer;
+  LBuyAccountNewPubkey : TAccountKey;
 begin
 begin
   Result := false;
   Result := false;
   AErrors := '';
   AErrors := '';
@@ -791,7 +796,7 @@ begin
   end;
   end;
   if (length(FData.payload)>CT_MaxPayloadSize) then begin
   if (length(FData.payload)>CT_MaxPayloadSize) then begin
     AErrors := 'Invalid Payload size:'+inttostr(length(FData.payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
     AErrors := 'Invalid Payload size:'+inttostr(length(FData.payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
-    If (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol>=CT_PROTOCOL_2) then begin
+    If (LCurrentProtocol>=CT_PROTOCOL_2) then begin
       Exit; // BUG from protocol 1
       Exit; // BUG from protocol 1
     end;
     end;
   end;
   end;
@@ -802,13 +807,13 @@ begin
   // V5 - Allow recipient-signed transactions. This is defined as
   // V5 - Allow recipient-signed transactions. This is defined as
   //  - Sender Account = Target Account
   //  - Sender Account = Target Account
   LRecipientSignable := TAccountComp.IsOperationRecipientSignable(LSender, LTarget, FData.Amount, LCurrentBlock, LCurrentProtocol);
   LRecipientSignable := TAccountComp.IsOperationRecipientSignable(LSender, LTarget, FData.Amount, LCurrentBlock, LCurrentProtocol);
-  LIsSwap := TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo);
+  LIsCoinSwap := TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo);
 
 
   if (FData.sender=FData.target) AND (NOT LRecipientSignable) then begin
   if (FData.sender=FData.target) AND (NOT LRecipientSignable) then begin
     AErrors := Format('Sender=Target and Target is not recipient-signable. Account: %d',[FData.sender]);
     AErrors := Format('Sender=Target and Target is not recipient-signable. Account: %d',[FData.sender]);
     Exit;
     Exit;
   end;
   end;
-  if (LRecipientSignable Or LIsSwap) then begin
+  if (LRecipientSignable Or LIsCoinSwap) then begin
     IF ((FData.amount < 0) or (FData.amount>CT_MaxTransactionAmount)) then begin
     IF ((FData.amount < 0) or (FData.amount>CT_MaxTransactionAmount)) then begin
       AErrors := Format('Recipient-signed transaction had invalid amount %d. Must be within 0 or %d.)',[FData.amount,CT_MaxTransactionAmount]);
       AErrors := Format('Recipient-signed transaction had invalid amount %d. Must be within 0 or %d.)',[FData.amount,CT_MaxTransactionAmount]);
       Exit;
       Exit;
@@ -860,11 +865,16 @@ begin
   // Is buy account ?
   // Is buy account ?
   if (FData.opTransactionStyle = buy_Account ) then begin
   if (FData.opTransactionStyle = buy_Account ) then begin
     {$region 'Buy Account Validation'}
     {$region 'Buy Account Validation'}
-    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+    if (LCurrentProtocol<CT_PROTOCOL_2) then begin
       AErrors := 'Buy account is not allowed on Protocol 1';
       AErrors := 'Buy account is not allowed on Protocol 1';
       exit;
       exit;
     end;
     end;
 
 
+    if (TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo)) then begin
+      AErrors := 'Atomic coin swap cannot be made purchasing, use standard tx instead';
+      Exit;
+    end;
+
     if (TAccountComp.IsAccountForSwap(LTarget.accountInfo) AND (LCurrentProtocol<CT_PROTOCOL_5)) then begin
     if (TAccountComp.IsAccountForSwap(LTarget.accountInfo) AND (LCurrentProtocol<CT_PROTOCOL_5)) then begin
       AErrors := 'Atomic swaps are not allowed until Protocol 5';
       AErrors := 'Atomic swaps are not allowed until Protocol 5';
       exit;
       exit;
@@ -901,14 +911,16 @@ begin
     end;
     end;
 
 
     If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
     If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
+    LBuyAccountNewPubkey := FData.new_accountkey;
     {$endregion}
     {$endregion}
     FPrevious_Seller_updated_block := LSeller.updated_block;
     FPrevious_Seller_updated_block := LSeller.updated_block;
   end else if // (is auto buy) OR (is transaction that can buy)
   end else if // (is auto buy) OR (is transaction that can buy)
               (FData.opTransactionStyle = transaction_with_auto_buy_account) OR
               (FData.opTransactionStyle = transaction_with_auto_buy_account) OR
+              (FData.opTransactionStyle = transaction_with_auto_atomic_swap) OR
               (
               (
                 (FData.opTransactionStyle = transaction) AND
                 (FData.opTransactionStyle = transaction) AND
                 (LCurrentProtocol >= CT_PROTOCOL_2) AND
                 (LCurrentProtocol >= CT_PROTOCOL_2) AND
-                (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, LCurrentBlock, FData.payload)) AND
+                (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, LCurrentBlock, LCurrentProtocol, FData.payload)) AND
                 ((LTarget.balance + FData.amount >= LTarget.accountInfo.price))
                 ((LTarget.balance + FData.amount >= LTarget.accountInfo.price))
               )  then begin
               )  then begin
     {$region 'Transaction Auto Buy Validation'}
     {$region 'Transaction Auto Buy Validation'}
@@ -917,48 +929,68 @@ begin
       exit;
       exit;
     end;
     end;
 
 
-    if (TAccountComp.IsAccountForSwap( LTarget.accountInfo ) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
-      AErrors := 'Tx-Buy atomic swaps are not allowed until Protocol 5';
-      exit;
-    end else If (LCurrentProtocol<CT_PROTOCOL_5) then begin
-      // the below line was a bug fix that introduced a new bug, and is retained here for
-      // V2-V4 consistency
-      //------
-      if Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
-      //------
-    end else if Not (TAccountComp.IsValidAccountKey(LTarget.accountInfo.new_publicKey,AErrors)) then exit;
+    If (LCurrentProtocol<CT_PROTOCOL_5) then begin
+      if (TAccountComp.IsAccountForSwap( LTarget.accountInfo )) then begin
+        AErrors := 'Tx-Buy atomic swaps are not allowed until Protocol 5';
+        exit;
+      end else begin
+        // the below line was a bug fix that introduced a new bug, and is retained here for
+        // V2-V4 consistency
+        //------
+        if Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
+        //------
+      end;
+    end;
+
+    // Check that stored "new_publicKey" is valid (when not in coin swap)
+    if (Not TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo)) and
+       (Not (TAccountComp.IsValidAccountKey(LTarget.accountInfo.new_publicKey,AErrors))) then exit;
+
+    // NOTE: This is a Transaction opereation (not a buy account operation) that
+    // has some "added" effects (private sale, swap...)
+    // in order to Store at the blockchain file we will fill this fields not specified
+    // on a transaction (otherwise JSON-RPC calls will not be abble to know what
+    // happened in this transaction as extra effect):
+    //
+    //  FData.opTransactionStyle: TOpTransactionStyle;
+    //  FData.AccountPrice : UInt64;
+    //  FData.SellerAccount : Cardinal;
+    //  FData.new_accountkey : TAccountKey;
 
 
     // Fill the purchase data
     // Fill the purchase data
-    FData.opTransactionStyle := transaction_with_auto_buy_account; // Set this data!
+    if TAccountComp.IsAccountForSale( LTarget.accountInfo ) then begin
+      FData.opTransactionStyle := transaction_with_auto_buy_account; // Set this data!
+    end else begin
+      FData.opTransactionStyle := transaction_with_auto_atomic_swap; // Set this data!
+    end;
     FData.AccountPrice := LTarget.accountInfo.price;
     FData.AccountPrice := LTarget.accountInfo.price;
     FData.SellerAccount := LTarget.accountInfo.account_to_pay;
     FData.SellerAccount := LTarget.accountInfo.account_to_pay;
     LSeller := ASafeBoxTransaction.Account(LTarget.accountInfo.account_to_pay);
     LSeller := ASafeBoxTransaction.Account(LTarget.accountInfo.account_to_pay);
     FPrevious_Seller_updated_block := LSeller.updated_block;
     FPrevious_Seller_updated_block := LSeller.updated_block;
-    FData.new_accountkey := LTarget.accountInfo.new_publicKey;
+    if TAccountComp.IsAccountForCoinSwap( LTarget.accountInfo ) then begin
+      // We will save extra info that account key has not changed
+      FData.new_accountkey := CT_TECDSA_Public_Nul;
+      // COIN SWAP: Ensure public key will not change and will be the same
+      LBuyAccountNewPubkey := LTarget.accountInfo.accountKey;
+    end else begin
+      FData.new_accountkey := LTarget.accountInfo.new_publicKey;
+      LBuyAccountNewPubkey := LTarget.accountInfo.new_publicKey;
+    end;
     {$endregion}
     {$endregion}
   end else if (FData.opTransactionStyle <> transaction) then begin
   end else if (FData.opTransactionStyle <> transaction) then begin
      AErrors := 'INTERNAL ERROR: 477C2A3C53C34E63A6B82C057741C44D';
      AErrors := 'INTERNAL ERROR: 477C2A3C53C34E63A6B82C057741C44D';
      exit;
      exit;
   end;
   end;
 
 
-  // final atomic coin swap checks for buy account flow
-  if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account]) AND (LTarget.accountInfo.state = as_ForAtomicCoinSwap) then begin
-    // Ensure the key for target doesn't change
-    if NOT TAccountComp.EqualAccountKeys(LTarget.accountInfo.accountKey, FData.new_accountkey) then begin
-      AErrors := 'Target key cannot be changed during atomic coin swap';
-      exit;
-    end;
-  end;
-
-  if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account]) then begin
+  if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account, transaction_with_auto_atomic_swap]) then begin
     // account purchase
     // account purchase
-    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+    if (LCurrentProtocol<CT_PROTOCOL_2) then begin
       AErrors := 'NOT ALLOWED ON PROTOCOL 1';
       AErrors := 'NOT ALLOWED ON PROTOCOL 1';
       exit;
       exit;
     end;
     end;
 
 
     if (LTarget.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) AND
     if (LTarget.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) AND
-       (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5) then begin
+       (LCurrentProtocol<CT_PROTOCOL_5) then begin
       AErrors := 'NOT ALLOWED UNTIL PROTOCOL 5';
       AErrors := 'NOT ALLOWED UNTIL PROTOCOL 5';
       exit;
       exit;
     end;
     end;
@@ -973,7 +1005,7 @@ begin
       FData.amount,
       FData.amount,
       LTarget.accountInfo.price,
       LTarget.accountInfo.price,
       FData.fee,
       FData.fee,
-      FData.new_accountkey,
+      LBuyAccountNewPubkey,
       FData.payload,
       FData.payload,
       LRecipientSignable,
       LRecipientSignable,
       AErrors
       AErrors
@@ -1063,10 +1095,16 @@ begin
       2 : Begin
       2 : Begin
         FData.opTransactionStyle := buy_account;
         FData.opTransactionStyle := buy_account;
         if (Not (Self is TOpBuyAccount)) then exit;
         if (Not (Self is TOpBuyAccount)) then exit;
-      End
+      End;
+      3 : FData.opTransactionStyle := transaction_with_auto_atomic_swap;
     else exit;
     else exit;
     end;
     end;
-    if (FData.opTransactionStyle in [transaction_with_auto_buy_account,buy_account]) then begin
+    if ((Self is TOpBuyAccount) and (FData.opTransactionStyle<>buy_account)) or
+       ((Not (Self is TOpBuyAccount)) and (FData.opTransactionStyle=buy_account)) then begin
+      // Protection invalid case added 20190705
+      Exit;
+    end;
+    if (FData.opTransactionStyle in [transaction_with_auto_buy_account,buy_account,transaction_with_auto_atomic_swap]) then begin
       Stream.Read(FData.AccountPrice,SizeOf(FData.AccountPrice));
       Stream.Read(FData.AccountPrice,SizeOf(FData.AccountPrice));
       Stream.Read(FData.SellerAccount,SizeOf(FData.SellerAccount));
       Stream.Read(FData.SellerAccount,SizeOf(FData.SellerAccount));
       if Stream.Read(FData.new_accountkey.EC_OpenSSL_NID,Sizeof(FData.new_accountkey.EC_OpenSSL_NID))<0 then exit;
       if Stream.Read(FData.new_accountkey.EC_OpenSSL_NID,Sizeof(FData.new_accountkey.EC_OpenSSL_NID))<0 then exit;
@@ -1097,7 +1135,7 @@ begin
       OperationResume.Receivers[0].Amount:=FData.amount;
       OperationResume.Receivers[0].Amount:=FData.amount;
       OperationResume.Receivers[0].Payload:=FData.payload;
       OperationResume.Receivers[0].Payload:=FData.payload;
     end;
     end;
-    buy_account,transaction_with_auto_buy_account : begin
+    buy_account, transaction_with_auto_buy_account, transaction_with_auto_atomic_swap : begin
       SetLength(OperationResume.Receivers,2);
       SetLength(OperationResume.Receivers,2);
       OperationResume.Receivers[0] := CT_TMultiOpReceiver_NUL;
       OperationResume.Receivers[0] := CT_TMultiOpReceiver_NUL;
       OperationResume.Receivers[0].Account:=FData.target;
       OperationResume.Receivers[0].Account:=FData.target;
@@ -1107,11 +1145,13 @@ begin
       OperationResume.Receivers[1].Account:=FData.SellerAccount;
       OperationResume.Receivers[1].Account:=FData.SellerAccount;
       OperationResume.Receivers[1].Amount:= FData.AccountPrice;
       OperationResume.Receivers[1].Amount:= FData.AccountPrice;
       OperationResume.Receivers[1].Payload:=FData.payload;
       OperationResume.Receivers[1].Payload:=FData.payload;
-      SetLength(OperationResume.Changers,1);
-      OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
-      OperationResume.Changers[0].Account := FData.target;
-      OperationResume.Changers[0].Changes_type := [public_key];
-      OperationResume.Changers[0].New_Accountkey := FData.new_accountkey;
+      if (Not TAccountComp.IsNullAccountKey(FData.new_accountkey)) then begin
+        SetLength(OperationResume.Changers,1);
+        OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
+        OperationResume.Changers[0].Account := FData.target;
+        OperationResume.Changers[0].Changes_type := [public_key];
+        OperationResume.Changers[0].New_Accountkey := FData.new_accountkey;
+      end;
     end;
     end;
   end;
   end;
 end;
 end;
@@ -1153,10 +1193,11 @@ begin
       transaction : b:=0;
       transaction : b:=0;
       transaction_with_auto_buy_account : b:=1;
       transaction_with_auto_buy_account : b:=1;
       buy_account : b:=2;
       buy_account : b:=2;
+      transaction_with_auto_atomic_swap : b:=3;
     else raise Exception.Create('ERROR DEV 20170424-1');
     else raise Exception.Create('ERROR DEV 20170424-1');
     end;
     end;
     Stream.Write(b,1);
     Stream.Write(b,1);
-    if (FData.opTransactionStyle in [transaction_with_auto_buy_account,buy_account]) then begin
+    if (FData.opTransactionStyle in [transaction_with_auto_buy_account,buy_account,transaction_with_auto_atomic_swap]) then begin
       Stream.Write(FData.AccountPrice,SizeOf(FData.AccountPrice));
       Stream.Write(FData.AccountPrice,SizeOf(FData.AccountPrice));
       Stream.Write(FData.SellerAccount,SizeOf(FData.SellerAccount));
       Stream.Write(FData.SellerAccount,SizeOf(FData.SellerAccount));
       Stream.Write(FData.new_accountkey.EC_OpenSSL_NID,Sizeof(FData.new_accountkey.EC_OpenSSL_NID));
       Stream.Write(FData.new_accountkey.EC_OpenSSL_NID,Sizeof(FData.new_accountkey.EC_OpenSSL_NID));
@@ -1182,7 +1223,7 @@ end;
 function TOpTransaction.SellerAccount: Int64;
 function TOpTransaction.SellerAccount: Int64;
 begin
 begin
   Case FData.opTransactionStyle of
   Case FData.opTransactionStyle of
-    transaction_with_auto_buy_account, buy_account : Result := FData.SellerAccount;
+    transaction_with_auto_buy_account, buy_account, transaction_with_auto_atomic_swap : Result := FData.SellerAccount;
   else Result:=inherited SellerAccount;
   else Result:=inherited SellerAccount;
   end;
   end;
 end;
 end;
@@ -1793,6 +1834,7 @@ end;
 function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;Var
 function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;Var
   account_signer, account_target : TAccount;
   account_signer, account_target : TAccount;
   LIsDelist, LIsSale, LIsPrivateSale, LIsPublicSale, LIsSwap, LIsAccountSwap, LIsCoinSwap : boolean;
   LIsDelist, LIsSale, LIsPrivateSale, LIsPublicSale, LIsSwap, LIsAccountSwap, LIsCoinSwap : boolean;
+  LCurrentProtocol : Integer;
 begin
 begin
   Result := false;
   Result := false;
   // Determine which flow this function will execute
   // Determine which flow this function will execute
@@ -1824,11 +1866,12 @@ begin
     LIsCoinSwap := false;
     LIsCoinSwap := false;
   end;
   end;
 
 
-  if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+  LCurrentProtocol := AccountTransaction.FreezedSafeBox.CurrentProtocol;
+  if (LCurrentProtocol<CT_PROTOCOL_2) then begin
     errors := 'List/Delist Account is not allowed on Protocol 1';
     errors := 'List/Delist Account is not allowed on Protocol 1';
     exit;
     exit;
   end;
   end;
-  if LIsSwap AND (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5) then begin
+  if LIsSwap AND (LCurrentProtocol<CT_PROTOCOL_5) then begin
     errors := 'Atomic Swaps are not allowed before Protocol 5';
     errors := 'Atomic Swaps are not allowed before Protocol 5';
     exit;
     exit;
   end;
   end;
@@ -1867,15 +1910,28 @@ begin
       errors := 'Account for sale price must be greater than 0';
       errors := 'Account for sale price must be greater than 0';
       exit;
       exit;
     end;
     end;
+
+    if (LIsAccountSwap) and (FData.account_price<>0) then begin
+      errors := 'Account for Account Swap must have 0 price';
+      Exit;
+    end;
+
+
     if (FData.locked_until_block > (AccountTransaction.FreezedSafeBox.BlocksCount + CT_MaxFutureBlocksLockedAccount)) then begin
     if (FData.locked_until_block > (AccountTransaction.FreezedSafeBox.BlocksCount + CT_MaxFutureBlocksLockedAccount)) then begin
       errors := 'Invalid locked block: Current block '+Inttostr(AccountTransaction.FreezedSafeBox.BlocksCount)+' cannot lock to block '+IntToStr(FData.locked_until_block);
       errors := 'Invalid locked block: Current block '+Inttostr(AccountTransaction.FreezedSafeBox.BlocksCount)+' cannot lock to block '+IntToStr(FData.locked_until_block);
       exit;
       exit;
     end;
     end;
-    if LIsPrivateSale OR LIsAccountSwap OR LIsCoinSwap then begin
+    if LIsPrivateSale OR LIsAccountSwap then begin
       If Not TAccountComp.IsValidAccountKey( FData.new_public_key, errors ) then begin
       If Not TAccountComp.IsValidAccountKey( FData.new_public_key, errors ) then begin
         errors := 'Invalid new public key: '+errors;
         errors := 'Invalid new public key: '+errors;
         exit;
         exit;
       end;
       end;
+    end else if (LCurrentProtocol>=CT_PROTOCOL_5) then begin
+      // COIN SWAP or PUBLIC SALE must set FData.new_public_key to NULL
+      if Not TAccountComp.IsNullAccountKey(FData.new_public_key) then begin
+        errors := 'Coin swap/Public sale needs a NULL new public key';
+        Exit;
+      end;
     end;
     end;
   end;
   end;
   if (FData.fee<0) Or (FData.fee>CT_MaxTransactionFee) then begin
   if (FData.fee<0) Or (FData.fee>CT_MaxTransactionFee) then begin
@@ -1937,7 +1993,7 @@ begin
   end;
   end;
 
 
   // Check signature
   // Check signature
-  If Not IsValidECDSASignature(account_signer.accountInfo.accountkey,AccountTransaction.FreezedSafeBox.CurrentProtocol,FData.sign) then begin
+  If Not IsValidECDSASignature(account_signer.accountInfo.accountkey,LCurrentProtocol,FData.sign) then begin
     errors := 'Invalid ECDSA signature';
     errors := 'Invalid ECDSA signature';
     Exit;
     Exit;
   end;
   end;
@@ -2042,8 +2098,6 @@ begin
 end;
 end;
 
 
 procedure TOpListAccount.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
 procedure TOpListAccount.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
-var
- LData : TMultiOpData;
 begin
 begin
   inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
   inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
   SetLength(OperationResume.Changers,1);
   SetLength(OperationResume.Changers,1);
@@ -2056,7 +2110,7 @@ begin
         end else begin
         end else begin
           if FData.account_state = as_ForAtomicAccountSwap then
           if FData.account_state = as_ForAtomicAccountSwap then
             OperationResume.Changers[0].Changes_type:=[list_for_account_swap, account_data]
             OperationResume.Changers[0].Changes_type:=[list_for_account_swap, account_data]
-          else if FData.account_state = as_ForAtomicAccountSwap then
+          else if FData.account_state = as_ForAtomicCoinSwap then
             OperationResume.Changers[0].Changes_type:=[list_for_coin_swap, account_data]
             OperationResume.Changers[0].Changes_type:=[list_for_coin_swap, account_data]
           else
           else
             OperationResume.Changers[0].Changes_type:=[list_for_private_sale];
             OperationResume.Changers[0].Changes_type:=[list_for_private_sale];
@@ -2288,7 +2342,14 @@ begin
   FData.payload := APayload;
   FData.payload := APayload;
   // V2: No need to store public key because it's at safebox. Saving at least 64 bytes!
   // V2: No need to store public key because it's at safebox. Saving at least 64 bytes!
   // FData.public_key := key.PublicKey;
   // FData.public_key := key.PublicKey;
-  FData.new_public_key := ANewPublicKey;
+  if (ANewAccountState in [as_ForAtomicCoinSwap]) then begin
+    // Force NULL new_public_key
+    FData.new_public_key := CT_TECDSA_Public_Nul;
+  end else begin
+    FData.new_public_key := ANewPublicKey;
+  end;
+
+
 
 
   if Assigned(AKey) then begin
   if Assigned(AKey) then begin
     FData.sign := TCrypto.ECDSASign(AKey.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FData.sign := TCrypto.ECDSASign(AKey.PrivateKey, GetDigestToSign(ACurrentProtocol));

+ 18 - 2
src/libraries/pascalcoin/UJSONFunctions.pas

@@ -43,6 +43,8 @@ Type
   TJSONValue = TJSONData;
   TJSONValue = TJSONData;
   {$ENDIF}
   {$ENDIF}
 
 
+  { TPCJSONData }
+
   TPCJSONData = Class
   TPCJSONData = Class
   private
   private
     FParent : TPCJSONData;
     FParent : TPCJSONData;
@@ -57,6 +59,7 @@ Type
     Function ToJSON(pretty : Boolean) : String;
     Function ToJSON(pretty : Boolean) : String;
     Procedure SaveToStream(Stream : TStream);
     Procedure SaveToStream(Stream : TStream);
     Procedure Assign(PCJSONData : TPCJSONData);
     Procedure Assign(PCJSONData : TPCJSONData);
+    class function JSONFormatSettings : TFormatSettings;
   End;
   End;
 
 
   TPCJSONDataClass = Class of TPCJSONData;
   TPCJSONDataClass = Class of TPCJSONData;
@@ -178,6 +181,8 @@ Type
 
 
 implementation
 implementation
 
 
+var _JSON_FormatSettings : TFormatSettings;
+
 Function UTF8JSONEncode(plainTxt : String; includeSeparator : Boolean) : String;
 Function UTF8JSONEncode(plainTxt : String; includeSeparator : Boolean) : String;
 Var ws : String;
 Var ws : String;
   i : Integer;
   i : Integer;
@@ -998,7 +1003,14 @@ begin
   inherited;
   inherited;
 end;
 end;
 
 
-class function TPCJSONData.ParseJSONValue(Const JSONObject: TBytes): TPCJSONData;
+class function TPCJSONData.JSONFormatSettings: TFormatSettings;
+begin
+  Result := _JSON_FormatSettings;
+end;
+
+
+class function TPCJSONData.ParseJSONValue(const JSONObject: TBytes
+  ): TPCJSONData;
 Var JS : TJSONValue;
 Var JS : TJSONValue;
   {$IFDEF FPC}
   {$IFDEF FPC}
   jss : TJSONStringType;
   jss : TJSONStringType;
@@ -1047,7 +1059,8 @@ begin
   Stream.Write(s[Low(s)],Length(s));
   Stream.Write(s[Low(s)],Length(s));
 end;
 end;
 
 
-class function TPCJSONData.ParseJSONValue(Const JSONObject: String): TPCJSONData;
+class function TPCJSONData.ParseJSONValue(const JSONObject: String
+  ): TPCJSONData;
 begin
 begin
   Result := ParseJSONValue( TEncoding.ASCII.GetBytes(JSONObject) );
   Result := ParseJSONValue( TEncoding.ASCII.GetBytes(JSONObject) );
 end;
 end;
@@ -1064,4 +1077,7 @@ end;
 
 
 initialization
 initialization
   _objectsCount := 0;
   _objectsCount := 0;
+  _JSON_FormatSettings := FormatSettings;
+  _JSON_FormatSettings.ThousandSeparator := ',';
+  _JSON_FormatSettings.DecimalSeparator := '.';
 end.
 end.