Repository: prebid/prebid-server
Pull request #2607 (2023-02-22 11:53:12)

Floors: signaling


Patch:
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index f256d875..baa10b93 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           # Resolves to empty string for push events and falls back to HEAD.
           ref: ${{ github.event.pull_request.head.sha }}
@@ -29,6 +29,6 @@ jobs:
           severity: 'CRITICAL,HIGH'
           
       - name: Upload Results To GitHub Security Tab
-        uses: github/codeql-action/upload-sarif@v1
+        uses: github/codeql-action/upload-sarif@v2
         with:
           sarif_file: 'trivy-results.sarif'
\ No newline at end of file
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index 5cf8cdb7..e85dc3de 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -10,12 +10,12 @@ jobs:
     
     steps:
     - name: Install Go
-      uses: actions/setup-go@v2
+      uses: actions/setup-go@v4
       with:
         go-version: 1.19.2
 
     - name: Checkout Merged Branch
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
 
     - name: Validate
       run: |
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 7d1fddc2..83b29709 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -16,12 +16,12 @@ jobs:
     
     steps:
     - name: Install Go
-      uses: actions/setup-go@v2
+      uses: actions/setup-go@v4
       with:
         go-version: ${{ matrix.go-version }}
 
     - name: Checkout Code
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
       with:
         # Resolves to empty string for push events and falls back to HEAD.
         ref: ${{ github.event.pull_request.head.sha }}
diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go
index aa093ec1..5c283f18 100644
--- a/adapters/appnexus/appnexus.go
+++ b/adapters/appnexus/appnexus.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"math/rand"
 	"net/http"
 	"strconv"
 	"strings"
@@ -14,6 +13,7 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/util/httputil"
+	"github.com/prebid/prebid-server/util/randomutil"
 
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/errortypes"
@@ -24,18 +24,43 @@ import (
 const defaultPlatformID int = 5
 
 type adapter struct {
-	URI            string
-	iabCategoryMap map[string]string
-	hbSource       int
+	URI             string
+	iabCategoryMap  map[string]string
+	hbSource        int
+	randomGenerator randomutil.RandomGenerator
 }
 
 var maxImpsPerReq = 10
 
+// Builder builds a new instance of the AppNexus adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		URI: config.Endpoint,
+		iabCategoryMap: map[string]string{
+			"1": "IAB20-3",
+			"9": "IAB5-3",
+		},
+		hbSource:        resolvePlatformID(config.PlatformID),
+		randomGenerator: randomutil.RandomNumberGenerator{},
+	}
+	return bidder, nil
+}
+
+func resolvePlatformID(platformID string) int {
+	if len(platformID) > 0 {
+		if val, err := strconv.Atoi(platformID); err == nil {
+			return val
+		}
+	}
+
+	return defaultPlatformID
+}
+
 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 
 	// appnexus adapter expects imp.displaymanagerver to be populated in openrtb2 endpoint
 	// but some SDKs will put it in imp.ext.prebid instead
-	displayManagerVer := buildDefaultDisplayManageVer(request)
+	displayManagerVer := buildDisplayManageVer(request)
 
 	var (
 		shouldGenerateAdPodId *bool
@@ -46,19 +71,26 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 
 	validImps := []openrtb2.Imp{}
 	for i := 0; i < len(request.Imp); i++ {
-		// If the preprocessing failed, the server won't be able to bid on this Imp. Delete it, and note the error.
-		memberId, shouldGenerateAdPodIdForImp, err := preprocess(&request.Imp[i], displayManagerVer)
+		appnexusExt, err := validateAndBuildAppNexusExt(&request.Imp[i])
 		if err != nil {
 			errs = append(errs, err)
 			continue
 		}
 
+		if err := buildRequestImp(&request.Imp[i], &appnexusExt, displayManagerVer); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		memberId := appnexusExt.Member
 		if memberId != "" {
 			if _, ok := memberIds[memberId]; !ok {
 				memberIds[memberId] = struct{}{}
 				uniqueMemberIds = append(uniqueMemberIds, memberId)
 			}
 		}
+
+		shouldGenerateAdPodIdForImp := appnexusExt.AdPodId
 		if shouldGenerateAdPodId == nil {
 			shouldGenerateAdPodId = &shouldGenerateAdPodIdForImp
 		} else if *shouldGenerateAdPodId != shouldGenerateAdPodIdForImp {
@@ -121,7 +153,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 	// If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same
 	// If  adpodId feature disabled and impressions number per pod is more than maxImpsPerReq  - divide those imps to several requests but do not include ad pod id
 	if isVIDEO == 1 && *shouldGenerateAdPodId {
-		requests, errors := buildAdPodRequests(imps, request, reqExt, requestURI)
+		requests, errors := a.buildAdPodRequests(imps, request, reqExt, requestURI)
 		return requests, append(errs, errors...)
 	}
 
@@ -129,6 +161,97 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 	return requests, append(errs, errors...)
 }
 
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if httputil.IsResponseStatusCodeNoContent(response) {
+		return nil, nil
+	}
+
+	if err := httputil.CheckResponseStatusCodeForErrors(response); err != nil {
+		return nil, []error{err}
+	}
+
+	var appnexusResponse openrtb2.BidResponse
+	if err := json.Unmarshal(response.Body, &appnexusResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	var errs []error
+	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	for _, sb := range appnexusResponse.SeatBid {
+		for i := range sb.Bid {
+			bid := sb.Bid[i]
+
+			var bidExt appnexusBidExt
+			if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			bidType, err := getMediaTypeForBid(&bidExt)
+			if err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			iabCategory, found := a.findIabCategoryForBid(&bidExt)
+			if found {
+				bid.Cat = []string{iabCategory}
+			} else if len(bid.Cat) > 1 {
+				//create empty categories array to force bid to be rejected
+				bid.Cat = make([]string, 0)
+			}
+
+			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+				Bid:          &bid,
+				BidType:      bidType,
+				BidVideo:     &openrtb_ext.ExtBidPrebidVideo{Duration: bidExt.Appnexus.CreativeInfo.Video.Duration},
+				DealPriority: bidExt.Appnexus.DealPriority,
+			})
+		}
+	}
+
+	if appnexusResponse.Cur != "" {
+		bidderResponse.Currency = appnexusResponse.Cur
+	}
+
+	return bidderResponse, errs
+}
+
+func validateAndBuildAppNexusExt(imp *openrtb2.Imp) (openrtb_ext.ExtImpAppnexus, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	var appnexusExt openrtb_ext.ExtImpAppnexus
+	if err := json.Unmarshal(bidderExt.Bidder, &appnexusExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	handleLegacyParams(&appnexusExt)
+
+	if err := validateAppnexusExt(&appnexusExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	return appnexusExt, nil
+}
+
+func handleLegacyParams(appnexusExt *openrtb_ext.ExtImpAppnexus) {
+	if appnexusExt.PlacementId == 0 && appnexusExt.DeprecatedPlacementId != 0 {
+		appnexusExt.PlacementId = appnexusExt.DeprecatedPlacementId
+	}
+	if appnexusExt.InvCode == "" && appnexusExt.LegacyInvCode != "" {
+		appnexusExt.InvCode = appnexusExt.LegacyInvCode
+	}
+	if appnexusExt.TrafficSourceCode == "" && appnexusExt.LegacyTrafficSourceCode != "" {
+		appnexusExt.TrafficSourceCode = appnexusExt.LegacyTrafficSourceCode
+	}
+	if appnexusExt.UsePaymentRule == nil && appnexusExt.DeprecatedUsePaymentRule != nil {
+		appnexusExt.UsePaymentRule = appnexusExt.DeprecatedUsePaymentRule
+	}
+}
+
 func groupByPods(imps []openrtb2.Imp) map[string]([]openrtb2.Imp) {
 	// find number of pods in response
 	podImps := make(map[string][]openrtb2.Imp)
@@ -187,41 +310,16 @@ func splitRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExt
 	return resArr, errs
 }
 
-// preprocess mutates the imp to get it ready to send to appnexus.
-//
-// It returns the member param, if it exists, and an error if anything went wrong during the preprocessing.
-func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, bool, error) {
-	var bidderExt adapters.ExtImpBidder
-	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
-		return "", false, err
-	}
-
-	var appnexusExt openrtb_ext.ExtImpAppnexus
-	if err := json.Unmarshal(bidderExt.Bidder, &appnexusExt); err != nil {
-		return "", false, err
-	}
-
-	// Accept legacy Appnexus parameters if we don't have modern ones
-	// Don't worry if both is set as validation rules should prevent, and this is temporary anyway.
-	if appnexusExt.PlacementId == 0 && appnexusExt.DeprecatedPlacementId != 0 {
-		appnexusExt.PlacementId = appnexusExt.DeprecatedPlacementId
-	}
-	if appnexusExt.InvCode == "" && appnexusExt.LegacyInvCode != "" {
-		appnexusExt.InvCode = appnexusExt.LegacyInvCode
-	}
-	if appnexusExt.TrafficSourceCode == "" && appnexusExt.LegacyTrafficSourceCode != "" {
-		appnexusExt.TrafficSourceCode = appnexusExt.LegacyTrafficSourceCode
-	}
-	if appnexusExt.UsePaymentRule == nil && appnexusExt.DeprecatedUsePaymentRule != nil {
-		appnexusExt.UsePaymentRule = appnexusExt.DeprecatedUsePaymentRule
-	}
-
+func validateAppnexusExt(appnexusExt *openrtb_ext.ExtImpAppnexus) error {
 	if appnexusExt.PlacementId == 0 && (appnexusExt.InvCode == "" || appnexusExt.Member == "") {
-		return "", false, &errortypes.BadInput{
+		return &errortypes.BadInput{
 			Message: "No placement or member+invcode provided",
 		}
 	}
+	return nil
+}
 
+func buildRequestImp(imp *openrtb2.Imp, appnexusExt *openrtb_ext.ExtImpAppnexus, displayManagerVer string) error {
 	if appnexusExt.InvCode != "" {
 		imp.TagID = appnexusExt.InvCode
 	}
@@ -246,8 +344,8 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, boo
 	}
 
 	// Populate imp.displaymanagerver if the SDK failed to do it.
-	if len(imp.DisplayManagerVer) == 0 && len(defaultDisplayManagerVer) > 0 {
-		imp.DisplayManagerVer = defaultDisplayManagerVer
+	if len(imp.DisplayManagerVer) == 0 && len(displayManagerVer) > 0 {
+		imp.DisplayManagerVer = displayManagerVer
 	}
 
 	impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{
@@ -257,12 +355,13 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, boo
 		UsePmtRule:        appnexusExt.UsePaymentRule,
 		PrivateSizes:      appnexusExt.PrivateSizes,
 	}}
+
 	var err error
 	if imp.Ext, err = json.Marshal(&impExt); err != nil {
-		return appnexusExt.Member, appnexusExt.AdPodId, err
+		return err
 	}
 
-	return appnexusExt.Member, appnexusExt.AdPodId, nil
+	return nil
 }
 
 func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string {
@@ -280,62 +379,6 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string {
 	return strings.Join(kvs, ",")
 }
 
-func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
-	if httputil.IsResponseStatusCodeNoContent(response) {
-		return nil, nil
-	}
-
-	if err := httputil.CheckResponseStatusCodeForErrors(response); err != nil {
-		return nil, []error{err}
-	}
-
-	var appnexusResponse openrtb2.BidResponse
-	if err := json.Unmarshal(response.Body, &appnexusResponse); err != nil {
-		return nil, []error{err}
-	}
-
-	var errs []error
-	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)
-	for _, sb := range appnexusResponse.SeatBid {
-		for i := range sb.Bid {
-			bid := sb.Bid[i]
-
-			var bidExt appnexusBidExt
-			if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
-				errs = append(errs, err)
-				continue
-			}
-
-			bidType, err := getMediaTypeForBid(&bidExt)
-			if err != nil {
-				errs = append(errs, err)
-				continue
-			}
-
-			iabCategory, found := a.findIabCategoryForBid(&bidExt)
-			if found {
-				bid.Cat = []string{iabCategory}
-			} else if len(bid.Cat) > 1 {
-				//create empty categories array to force bid to be rejected
-				bid.Cat = make([]string, 0)
-			}
-
-			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
-				Bid:          &bid,
-				BidType:      bidType,
-				BidVideo:     &openrtb_ext.ExtBidPrebidVideo{Duration: bidExt.Appnexus.CreativeInfo.Video.Duration},
-				DealPriority: bidExt.Appnexus.DealPriority,
-			})
-		}
-	}
-
-	if appnexusResponse.Cur != "" {
-		bidderResponse.Currency = appnexusResponse.Cur
-	}
-
-	return bidderResponse, errs
-}
-
 // getMediaTypeForBid determines which type of bid.
 func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) {
 	switch bid.Appnexus.BidType {
@@ -367,30 +410,7 @@ func appendMemberId(uri string, memberId string) string {
 	return uri + "?member_id=" + memberId
 }
 
-// Builder builds a new instance of the AppNexus adapter for the given bidder with the given config.
-func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
-	bidder := &adapter{
-		URI: config.Endpoint,
-		iabCategoryMap: map[string]string{
-			"1": "IAB20-3",
-			"9": "IAB5-3",
-		},
-		hbSource: resolvePlatformID(config.PlatformID),
-	}
-	return bidder, nil
-}
-
-func resolvePlatformID(platformID string) int {
-	if len(platformID) > 0 {
-		if val, err := strconv.Atoi(platformID); err == nil {
-			return val
-		}
-	}
-
-	return defaultPlatformID
-}
-
-func buildDefaultDisplayManageVer(req *openrtb2.BidRequest) string {
+func buildDisplayManageVer(req *openrtb2.BidRequest) string {
 	if req.App == nil {
 		return ""
 	}
@@ -408,12 +428,12 @@ func buildDefaultDisplayManageVer(req *openrtb2.BidRequest) string {
 	return fmt.Sprintf("%s-%s", source, version)
 }
 
-func buildAdPodRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string) ([]*adapters.RequestData, []error) {
+func (a *adapter) buildAdPodRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string) ([]*adapters.RequestData, []error) {
 	var errs []error
 	podImps := groupByPods(imps)
 	requests := make([]*adapters.RequestData, 0, len(podImps))
 	for _, podImps := range podImps {
-		requestExtension.Appnexus.AdPodId = fmt.Sprint(rand.Int63())
+		requestExtension.Appnexus.AdPodId = fmt.Sprint(a.randomGenerator.GenerateInt63())
 
 		reqs, errors := splitRequests(podImps, request, requestExtension, uri)
 		requests = append(requests, reqs...)
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index faa43e93..e709323c 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -47,6 +47,7 @@ type rubiconExtImpBidder struct {
 	Prebid  *openrtb_ext.ExtImpPrebid `json:"prebid"`
 	Bidder  openrtb_ext.ExtImpRubicon `json:"bidder"`
 	Gpid    string                    `json:"gpid"`
+	Skadn   json.RawMessage           `json:"skadn,omitempty"`
 	Data    json.RawMessage           `json:"data"`
 	Context rubiconContext            `json:"context"`
 }
@@ -77,8 +78,9 @@ type rubiconImpExtRPTrack struct {
 }
 
 type rubiconImpExt struct {
-	RP   rubiconImpExtRP `json:"rp,omitempty"`
-	GPID string          `json:"gpid,omitempty"`
+	RP    rubiconImpExtRP `json:"rp,omitempty"`
+	GPID  string          `json:"gpid,omitempty"`
+	Skadn json.RawMessage `json:"skadn,omitempty"`
 }
 
 type rubiconImpExtRP struct {
@@ -96,11 +98,11 @@ type rubiconDataExt struct {
 }
 
 type rubiconUserExt struct {
-	Consent     string           `json:"consent,omitempty"`
 	Eids        []openrtb2.EID   `json:"eids,omitempty"`
 	RP          rubiconUserExtRP `json:"rp"`
 	LiverampIdl string           `json:"liveramp_idl,omitempty"`
 	Data        json.RawMessage  `json:"data,omitempty"`
+	Consent     string           `json:"consent,omitempty"`
 }
 
 type rubiconSiteExtRP struct {
@@ -231,6 +233,19 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 }
 
 func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	requestCopy := request
+	if request.User != nil {
+		userCopy := *request.User
+		requestCopy.User = &userCopy
+	}
+
+	err := openrtb_ext.ConvertUpTo26(&openrtb_ext.RequestWrapper{BidRequest: requestCopy})
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	request = requestCopy
+
 	numRequests := len(request.Imp)
 	requestData := make([]*adapters.RequestData, 0, numRequests)
 	headers := http.Header{}
@@ -268,7 +283,8 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 				Target: target,
 				Track:  rubiconImpExtRPTrack{Mint: "", MintVersion: ""},
 			},
-			GPID: bidderExt.Gpid,
+			GPID:  bidderExt.Gpid,
+			Skadn: bidderExt.Skadn,
 		}
 
 		imp.Ext, err = json.Marshal(&impExt)
@@ -305,36 +321,30 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 			userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}}
 			userBuyerUID := userCopy.BuyerUID
 
-			if request.User.Ext != nil {
-				var userExt *openrtb_ext.ExtUser
-				if err = json.Unmarshal(userCopy.Ext, &userExt); err != nil {
-					errs = append(errs, &errortypes.BadInput{
-						Message: err.Error(),
-					})
-					continue
+			if len(userCopy.EIDs) > 0 {
+				userExtRP.Eids = userCopy.EIDs
+
+				if userBuyerUID == "" {
+					userBuyerUID = extractUserBuyerUID(userExtRP.Eids)
 				}
-				userExtRP.Consent = userExt.Consent
-				userExtRP.Eids = userExt.Eids
 
-				// set user.ext.tpid
-				if len(userExt.Eids) > 0 {
-					if userBuyerUID == "" {
-						userBuyerUID = extractUserBuyerUID(userExt.Eids)
-					}
+				mappedRubiconUidsParam, errors := getSegments(userExtRP.Eids)
+				if len(errors) > 0 {
+					errs = append(errs, errors...)
+					continue
+				}
 
-					mappedRubiconUidsParam, errors := getSegments(userExt.Eids)
-					if len(errors) > 0 {
-						errs = append(errs, errors...)
-						continue
-					}
+				if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil {
+					errs = append(errs, err)
+					continue
+				}
 
-					if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil {
-						errs = append(errs, err)
-						continue
-					}
+				userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl
+			}
 
-					userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl
-				}
+			if userCopy.Consent != "" {
+				userExtRP.Consent = userCopy.Consent
+				userCopy.Consent = ""
 			}
 
 			userCopy.Ext, err = json.Marshal(&userExtRP)
@@ -346,6 +356,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 			userCopy.Yob = 0
 			userCopy.Gender = ""
 			userCopy.BuyerUID = userBuyerUID
+			userCopy.EIDs = nil
 
 			rubiconRequest.User = &userCopy
 		}
@@ -373,10 +384,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 				videoSizeId = resolvedSizeId
 			}
 
-			// if imp.ext.is_rewarded_inventory = 1, set imp.video.ext.videotype = "rewarded"
+			// if imp.rwdd = 1, set imp.video.ext.videotype = "rewarded"
 			var videoType = ""
-			if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 {
+			if imp.Rwdd == 1 {
 				videoType = "rewarded"
+				imp.Rwdd = 0
 			}
 			videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, VideoType: videoType, RP: rubiconVideoExtRP{SizeID: videoSizeId}}
 			videoCopy.Ext, err = json.Marshal(&videoExt)
@@ -452,6 +464,73 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 			rubiconRequest.App = &appCopy
 		}
 
+		if request.Source != nil || rubiconExt.PChain != "" {
+			var sourceCopy openrtb2.Source
+			if request.Source != nil {
+				sourceCopy = *request.Source
+			} else {
+				sourceCopy = openrtb2.Source{}
+			}
+
+			if sourceCopy.SChain != nil {
+				var sourceCopyExt openrtb_ext.ExtSource
+				if sourceCopy.Ext != nil {
+					if err = json.Unmarshal(sourceCopy.Ext, &sourceCopyExt); err != nil {
+						errs = append(errs, &errortypes.BadInput{Message: err.Error()})
+						continue
+					}
+				} else {
+					sourceCopyExt = openrtb_ext.ExtSource{}
+				}
+
+				sourceCopyExt.SChain = sourceCopy.SChain
+				sourceCopy.SChain = nil
+
+				sourceCopy.Ext, err = json.Marshal(&sourceCopyExt)
+				if err != nil {
+					errs = append(errs, err)
+					continue
+				}
+			}
+
+			if rubiconExt.PChain != "" {
+				sourceCopy.PChain = rubiconExt.PChain
+			}
+
+			rubiconRequest.Source = &sourceCopy
+		}
+
+		if request.Regs != nil && (request.Regs.GDPR != nil || request.Regs.USPrivacy != "") {
+			regsCopy := *request.Regs
+
+			var regsCopyExt openrtb_ext.ExtRegs
+			if regsCopy.Ext != nil {
+				if err = json.Unmarshal(regsCopy.Ext, &regsCopyExt); err != nil {
+					errs = append(errs, &errortypes.BadInput{Message: err.Error()})
+					continue
+				}
+			} else {
+				regsCopyExt = openrtb_ext.ExtRegs{}
+			}
+
+			if regsCopy.GDPR != nil {
+				regsCopyExt.GDPR = regsCopy.GDPR
+			}
+			if regsCopy.USPrivacy != "" {
+				regsCopyExt.USPrivacy = regsCopy.USPrivacy
+			}
+
+			regsCopy.Ext, err = json.Marshal(&regsCopyExt)
+			if err != nil {
+				errs = append(errs, err)
+				continue
+			}
+			regsCopy.GDPR = nil
+			regsCopy.USPrivacy = ""
+
+			rubiconRequest.Regs = &regsCopy
+		}
+
 		reqBadv := request.BAdv
 		if reqBadv != nil {
 			if len(reqBadv) > badvLimitSize {
diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go
index 881e6120..6546842a 100644
--- a/adapters/taboola/taboola.go
+++ b/adapters/taboola/taboola.go
@@ -11,13 +11,14 @@ import (
 	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"net/http"
-	"net/url"
+	"strconv"
+	"strings"
 	"text/template"
 )
 
 type adapter struct {
 	endpoint *template.Template
-	hostName string
+	gvlID    string
 }
 
 // Builder builds a new instance of Taboola adapter for the given bidder with the given config.
@@ -27,20 +28,14 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
 	}
 
-	hostName := ""
-	if server.ExternalUrl != "" {
-		parsedUrl, err := url.Parse(server.ExternalUrl)
-		if err == nil && parsedUrl != nil {
-			hostName = parsedUrl.Host
-		}
+	gvlID := ""
+	if server.GvlID > 0 {
+		gvlID = strconv.Itoa(server.GvlID)
 	}
 
-	if err != nil {
-		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
-	}
 	bidder := &adapter{
 		endpoint: endpointTemplate,
-		hostName: hostName,
+		gvlID:    gvlID,
 	}
 	return bidder, nil
 }
@@ -98,6 +93,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R
 	for _, seatBid := range response.SeatBid {
 		for i := range seatBid.Bid {
 			bidType, err := getMediaType(seatBid.Bid[i].ImpID, request.Imp)
+			resolveMacros(&seatBid.Bid[i])
 			if err != nil {
 				errs = append(errs, err)
 				continue
@@ -149,7 +145,7 @@ func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestD
 
 // Builds endpoint url based on adapter-specific pub settings from imp.ext
 func (a *adapter) buildEndpointURL(publisherId string, mediaType string) (string, error) {
-	endpointParams := macros.EndpointTemplateParams{PublisherID: publisherId, MediaType: mediaType, Host: a.hostName}
+	endpointParams := macros.EndpointTemplateParams{PublisherID: publisherId, MediaType: mediaType, GvlID: a.gvlID}
 	resolvedUrl, err := macros.ResolveMacros(a.endpoint, endpointParams)
 	if err != nil {
 		return "", err
@@ -176,10 +172,15 @@ func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*ope
 			errs = append(errs, err)
 			continue
 		}
-		if taboolaExt.TagId != "" {
-			imp.TagID = taboolaExt.TagId
-			modifiedRequest.Imp[i] = imp
+
+		tagId := taboolaExt.TagID
+		if len(taboolaExt.TagID) < 1 {
+			tagId = taboolaExt.TagId
 		}
+
+		imp.TagID = tagId
+		modifiedRequest.Imp[i] = imp
+
 		if taboolaExt.BidFloor != 0 {
 			imp.BidFloor = taboolaExt.BidFloor
 			modifiedRequest.Imp[i] = imp
@@ -288,3 +289,11 @@ func overrideBidRequestImp(originBidRequest *openrtb2.BidRequest, imp []openrtb2
 	bidRequestResult.Imp = imp
 	return &bidRequestResult
 }
+
+func resolveMacros(bid *openrtb2.Bid) {
+	if bid != nil {
+		price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
+		bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
+		bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
+	}
+}
diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go
index 06059267..0e9a376f 100644
--- a/adapters/tappx/tappx.go
+++ b/adapters/tappx/tappx.go
@@ -134,9 +134,9 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in
 	tappxHost := "tappx.com"
 	isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2,3}|test)$`, []byte(params.Endpoint))
 	if isNewEndpoint {
-		tappxHost = params.Endpoint + ".pub." + tappxHost + "/rtb/"
+		tappxHost = params.Endpoint + ".pub.tappx.com/rtb/"
 	} else {
-		tappxHost = "ssp.api." + tappxHost + "/rtb/v2/"
+		tappxHost = "ssp.api.tappx.com/rtb/v2/"
 	}
 
 	endpointParams := macros.EndpointTemplateParams{Host: tappxHost}
diff --git a/config/accounts.go b/config/accounts.go
index 2a9deb49..77803309 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -3,6 +3,7 @@ package config
 import (
 	"encoding/json"
 	"fmt"
+	"math"
 	"strings"
 
 	"github.com/prebid/go-gdpr/consentconstants"
@@ -60,6 +61,8 @@ type AccountPriceFloors struct {
 	AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"`
 	EnforceDealFloors      bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"`
 	UseDynamicData         bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"`
+	MaxRule                int  `mapstructure:"max_rules" json:"max_rules"`
+	MaxSchemaDims          int  `mapstructure:"max_schema_dims" json:"max_schema_dims"`
 }
 
 func (pf *AccountPriceFloors) validate(errs []error) []error {
@@ -67,6 +70,15 @@ func (pf *AccountPriceFloors) validate(errs []error) []error {
 	if pf.EnforceFloorsRate < 0 || pf.EnforceFloorsRate > 100 {
 		errs = append(errs, fmt.Errorf(`account_defaults.price_floors.enforce_floors_rate should be between 0 and 100`))
 	}
+
+	if pf.MaxRule < 0 || pf.MaxRule > math.MaxInt32 {
+		errs = append(errs, fmt.Errorf(`account_defaults.price_floors.max_rules should be between 0 and %v`, math.MaxInt32))
+	}
+
+	if pf.MaxSchemaDims < 0 || pf.MaxSchemaDims > 20 {
+		errs = append(errs, fmt.Errorf(`account_defaults.price_floors.max_schema_dims should be between 0 and 20`))
+	}
+
 	return errs
 }
 
diff --git a/config/config.go b/config/config.go
index 066748c4..292b1391 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1006,6 +1006,8 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
 	v.SetDefault("account_defaults.price_floors.adjust_for_bid_adjustment", true)
 	v.SetDefault("account_defaults.price_floors.enforce_deal_floors", false)
 	v.SetDefault("account_defaults.price_floors.use_dynamic_data", false)
+	v.SetDefault("account_defaults.price_floors.max_rules", 100)
+	v.SetDefault("account_defaults.price_floors.max_schema_dims", 3)
 
 	v.SetDefault("certificates_file", "")
 	v.SetDefault("auto_gen_source_tid", true)
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index d0d2bcf6..5554d41a 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -80,8 +80,6 @@ func NewAmpEndpoint(
 		IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed,
 	}
 
-	hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAmp, metricsEngine)
-
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
@@ -100,7 +98,7 @@ func NewAmpEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutor}).AmpAuction), nil
+		hookExecutionPlanBuilder}).AmpAuction), nil
 
 }
 
@@ -113,6 +111,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	// to compute the auction timeout.
 	start := time.Now()
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAmp, deps.metricsEngine)
+
 	ao := analytics.AmpObject{
 		Status:    http.StatusOK,
 		Errors:    make([]error, 0),
@@ -150,13 +150,13 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
 	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
-	_, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(r, nilBody)
+	_, rejectErr := hookExecutor.ExecuteEntrypointStage(r, nilBody)
 	reqWrapper, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errL := deps.parseAmpRequest(r)
 	ao.Errors = append(ao.Errors, errL...)
 	// Process reject after parsing amp request, so we can use reqWrapper.
 	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
 	if rejectErr != nil {
-		labels, ao = rejectAmpRequest(*rejectErr, w, deps.hookExecutor, reqWrapper, nil, labels, ao, nil)
+		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, nil, labels, ao, nil)
 		return
 	}
 
@@ -233,7 +233,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		StoredBidResponses:         storedBidResponses,
 		BidderImpReplaceImpID:      bidderImpReplaceImp,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookExecutor,
 		QueryParams:                r.URL.Query(),
 	}
 
@@ -261,11 +261,11 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	}
 
 	if isRejectErr {
-		labels, ao = rejectAmpRequest(*rejectErr, w, deps.hookExecutor, reqWrapper, account, labels, ao, errL)
+		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, account, labels, ao, errL)
 		return
 	}
 
-	labels, ao = sendAmpResponse(w, deps.hookExecutor, response, reqWrapper, account, labels, ao, errL)
+	labels, ao = sendAmpResponse(w, hookExecutor, response, reqWrapper, account, labels, ao, errL)
 }
 
 func rejectAmpRequest(
@@ -590,15 +590,16 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2
 		req.TMax = int64(*ampParams.Timeout) - deps.cfg.AMPTimeoutAdjustment
 	}
 
-	if err := setTargeting(req, ampParams.Targeting); err != nil {
-		return []error{err}
+	var errors []error
+	if warn := setTargeting(req, ampParams.Targeting); warn != nil {
+		errors = append(errors, warn)
 	}
 
 	if err := setTrace(req, ampParams.Trace); err != nil {
-		return []error{err}
+		return append(errors, err)
 	}
 
-	return nil
+	return errors
 }
 
 // setConsentedProviders sets the addtl_consent value to user.ext.ConsentedProvidersSettings.consented_providers
@@ -652,7 +653,12 @@ func setTargeting(req *openrtb2.BidRequest, targeting string) error {
 	if len(req.Imp[0].Ext) > 0 {
 		newImpExt, err := jsonpatch.MergePatch(req.Imp[0].Ext, targetingData)
 		if err != nil {
-			return fmt.Errorf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error())
+			warn := errortypes.Warning{
+				WarningCode: errortypes.BadInputErrorCode,
+				Message:     fmt.Sprintf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error()),
+			}
+
+			return &warn
 		}
 		req.Imp[0].Ext = newImpExt
 		return nil
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 137b66c5..350edb76 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -99,8 +99,6 @@ func NewEndpoint(
 		IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed,
 	}
 
-	hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAuction, metricsEngine)
-
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
@@ -119,7 +117,7 @@ func NewEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutor}).Auction), nil
+		hookExecutionPlanBuilder}).Auction), nil
 }
 
 type endpointDeps struct {
@@ -140,7 +138,7 @@ type endpointDeps struct {
 	debugLogRegexp            *regexp.Regexp
 	privateNetworkIPValidator iputil.IPValidator
 	storedRespFetcher         stored_requests.Fetcher
-	hookExecutor              hookexecution.HookStageExecutor
+	hookExecutionPlanBuilder  hooks.ExecutionPlanBuilder
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -152,6 +150,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	// to compute the auction timeout.
 	start := time.Now()
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 	ao := analytics.AuctionObject{
 		Status:    http.StatusOK,
 		Errors:    make([]error, 0),
@@ -173,13 +173,13 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
-	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels)
+	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels, hookExecutor)
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
 		return
 	}
 
 	if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil {
-		labels, ao = rejectAuctionRequest(*rejectErr, w, deps.hookExecutor, req.BidRequest, account, labels, ao)
+		labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao)
 		return
 	}
 
@@ -226,7 +226,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		StoredBidResponses:         storedBidResponses,
 		BidderImpReplaceImpID:      bidderImpReplaceImp,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookExecutor,
 	}
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
 	ao.Request = req.BidRequest
@@ -246,11 +246,11 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		ao.Errors = append(ao.Errors, err)
 		return
 	} else if isRejectErr {
-		labels, ao = rejectAuctionRequest(*rejectErr, w, deps.hookExecutor, req.BidRequest, account, labels, ao)
+		labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao)
 		return
 	}
 
-	labels, ao = sendAuctionResponse(w, deps.hookExecutor, response, req.BidRequest, account, labels, ao)
+	labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao)
 }
 
 func rejectAuctionRequest(
@@ -329,7 +329,7 @@ func sendAuctionResponse(
 // possible, it will return errors with messages that suggest improvements.
 //
 // If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) {
+func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels, hookExecutor hookexecution.HookStageExecutor) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) {
 	req = &openrtb_ext.RequestWrapper{}
 	req.BidRequest = &openrtb2.BidRequest{}
 	errs = nil
@@ -352,7 +352,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		}
 	}
 
-	requestJson, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson)
+	requestJson, rejectErr := hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson)
 	if rejectErr != nil {
 		errs = []error{rejectErr}
 		if err = json.Unmarshal(requestJson, req.BidRequest); err != nil {
@@ -395,8 +395,8 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		return
 	}
 
-	deps.hookExecutor.SetAccount(account)
-	requestJson, rejectErr = deps.hookExecutor.ExecuteRawAuctionStage(requestJson)
+	hookExecutor.SetAccount(account)
+	requestJson, rejectErr = hookExecutor.ExecuteRawAuctionStage(requestJson)
 	if rejectErr != nil {
 		errs = []error{rejectErr}
 		if err = json.Unmarshal(requestJson, req.BidRequest); err != nil {
@@ -406,7 +406,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 	}
 
 	// retrieve storedRequests and storedImps once more in case stored data was changed by the raw auction hook
-	if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), deps.hookExecutor.GetOutcomes()) {
+	if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), hookExecutor.GetOutcomes()) {
 		impInfo, errs = parseImpInfo(requestJson)
 		if len(errs) > 0 {
 			return nil, nil, nil, nil, nil, nil, errs
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index d27d7084..7ceb7656 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -18,6 +18,7 @@ import (
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/ortb"
 	jsonpatch "gopkg.in/evanphx/json-patch.v4"
@@ -88,7 +89,7 @@ func NewVideoEndpoint(
 		videoEndpointRegexp,
 		ipValidator,
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{}}).VideoAuctionEndpoint), nil
+		hooks.EmptyPlanBuilder{}}).VideoAuctionEndpoint), nil
 }
 
 /*
@@ -296,7 +297,6 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	secGPC := r.Header.Get("Sec-GPC")
-
 	auctionRequest := exchange.AuctionRequest{
 		BidRequestWrapper:          bidReqWrapper,
 		Account:                    *account,
@@ -306,7 +306,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		LegacyLabels:               labels,
 		GlobalPrivacyControlHeader: secGPC,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookexecution.EmptyHookExecutor{},
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
diff --git a/exchange/exchange.go b/exchange/exchange.go
index aeba357f..4b8b1921 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -22,6 +22,7 @@ import (
 	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/firstpartydata"
+	"github.com/prebid/prebid-server/floors"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/metrics"
@@ -75,6 +76,7 @@ type exchange struct {
 	server                   config.Server
 	bidValidationEnforcement config.Validations
 	requestSplitter          requestSplitter
+	floor                    config.PriceFloors
 }
 
 // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
@@ -168,6 +170,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid
 		server:                   config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter},
 		bidValidationEnforcement: cfg.Validations,
 		requestSplitter:          requestSplitter,
+		floor:                    cfg.PriceFloors,
 	}
 }
 
@@ -216,6 +219,7 @@ type BidderRequest struct {
 }
 
 func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) {
+	var floorErrs []error
 	err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper)
 	if err != nil {
 		return nil, err
@@ -247,6 +251,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
 	}
 
+	// Get currency rates conversions for the auction
+	conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions)
+
+	if e.floor.Enabled {
+		floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions)
+	}
+
 	responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog)
 
 	// save incoming request with stored requests (if applicable) to return in debug logs
@@ -262,7 +273,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	}
 	e.me.RecordDebugRequest(responseDebugAllow || accountDebugAllow, r.PubID)
 
-	if r.RequestType == metrics.ReqTypeORTB2Web || r.RequestType == metrics.ReqTypeORTB2App {
+	if r.RequestType == metrics.ReqTypeORTB2Web ||
+		r.RequestType == metrics.ReqTypeORTB2App ||
+		r.RequestType == metrics.ReqTypeAMP {
 		//Extract First party data for auction endpoint only
 		resolvedFPD, fpdErrors := firstpartydata.ExtractFPDForBidders(r.BidRequestWrapper)
 		if len(fpdErrors) > 0 {
@@ -295,6 +308,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		SChain: requestExt.GetSChain(),
 	}
 	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, r, requestExtLegacy, gdprDefaultValue)
+	errs = append(errs, floorErrs...)
 
 	e.me.RecordRequestPrivacy(privacyLabels)
 
@@ -307,9 +321,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	auctionCtx, cancel := e.makeAuctionContext(ctx, cacheInstructions.cacheBids)
 	defer cancel()
 
-	// Get currency rates conversions for the auction
-	conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions)
-
 	var adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 	var adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra
 	var fledge *openrtb_ext.Fledge
diff --git a/floors/floors.go b/floors/floors.go
new file mode 100644
index 00000000..efed0d11
--- /dev/null
+++ b/floors/floors.go
@@ -0,0 +1,209 @@
+package floors
+
+import (
+	"errors"
+	"math"
+	"math/rand"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type Price struct {
+	FloorMin    float64
+	FloorMinCur string
+}
+
+const (
+	defaultDelimiter string = "|"
+	catchAll         string = "*"
+	skipRateMin      int    = 0
+	skipRateMax      int    = 100
+	modelWeightMax   int    = 100
+	modelWeightMin   int    = 1
+	enforceRateMin   int    = 0
+	enforceRateMax   int    = 100
+)
+
+// EnrichWithPriceFloors checks for floors enabled in account and request and selects floors data from dynamic fetched if present
+// else selects floors data from req.ext.prebid.floors and update request with selected floors details
+func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions) []error {
+
+	if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil {
+		return []error{errors.New("Empty bidrequest")}
+	}
+
+	if isPriceFloorsDisabled(account, bidRequestWrapper) {
+		return []error{errors.New("Floors feature is disabled at account or in the request")}
+	}
+
+	floors, err := resolveFloors(account, bidRequestWrapper, conversions)
+
+	updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions)
+	updateFloorsInRequest(bidRequestWrapper, floors)
+	return append(err, updateReqErrs...)
+}
+
+// updateBidRequestWithFloors will update imp.bidfloor and imp.bidfloorcur based on rules matching
+func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, request *openrtb_ext.RequestWrapper, conversions currency.Conversions) []error {
+	var (
+		floorErrList []error
+		floorVal     float64
+	)
+
+	if extFloorRules == nil || extFloorRules.Data == nil || len(extFloorRules.Data.ModelGroups) == 0 {
+		return []error{}
+	}
+
+	modelGroup := extFloorRules.Data.ModelGroups[0]
+	if modelGroup.Schema.Delimiter == "" {
+		modelGroup.Schema.Delimiter = defaultDelimiter
+	}
+
+	extFloorRules.Skipped = new(bool)
+	if shouldSkipFloors(modelGroup.SkipRate, extFloorRules.Data.SkipRate, extFloorRules.SkipRate, rand.Intn) {
+		*extFloorRules.Skipped = true
+		return []error{}
+	}
+
+	floorErrList = validateFloorRulesAndLowerValidRuleKey(modelGroup.Schema, modelGroup.Schema.Delimiter, modelGroup.Values)
+	if len(modelGroup.Values) > 0 {
+		for _, imp := range request.GetImp() {
+			desiredRuleKey := createRuleKey(modelGroup.Schema, request, imp)
+			matchedRule, isRuleMatched := findRule(modelGroup.Values, modelGroup.Schema.Delimiter, desiredRuleKey)
+			floorVal = modelGroup.Default
+			if isRuleMatched {
+				floorVal = modelGroup.Values[matchedRule]
+			}
+
+			// No rule is matched or no default value provided or non-zero bidfloor not provided
+			if floorVal == 0.0 {
+				continue
+			}
+
+			floorMinVal, floorCur, err := getMinFloorValue(extFloorRules, imp, conversions)
+			if err == nil {
+				floorVal = roundToFourDecimals(floorVal)
+				bidFloor := floorVal
+				if floorMinVal > 0.0 && floorVal < floorMinVal {
+					bidFloor = floorMinVal
+				}
+
+				imp.BidFloor = bidFloor
+				imp.BidFloorCur = floorCur
+
+				if isRuleMatched {
+					err = updateImpExtWithFloorDetails(imp, matchedRule, floorVal, imp.BidFloor)
+					if err != nil {
+						floorErrList = append(floorErrList, err)
+					}
+				}
+			} else {
+				floorErrList = append(floorErrList, err)
+			}
+		}
+	}
+	return floorErrList
+}
+
+// roundToFourDecimals retuns given value to 4 decimal points
+func roundToFourDecimals(in float64) float64 {
+	return math.Round(in*10000) / 10000
+}
+
+// isPriceFloorsDisabled check for floors are disabled at account or request level
+func isPriceFloorsDisabled(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper) bool {
+	return isPriceFloorsDisabledForAccount(account) || isPriceFloorsDisabledForRequest(bidRequestWrapper)
+}
+
+// isPriceFloorsDisabledForAccount check for floors are disabled at account
+func isPriceFloorsDisabledForAccount(account config.Account) bool {
+	return !account.PriceFloors.Enabled
+}
+
+// isPriceFloorsDisabledForRequest check for floors are disabled at request
+func isPriceFloorsDisabledForRequest(bidRequestWrapper *openrtb_ext.RequestWrapper) bool {
+	requestExt, err := bidRequestWrapper.GetRequestExt()
+	if err == nil {
+		if prebidExt := requestExt.GetPrebid(); prebidExt != nil && prebidExt.Floors != nil && !prebidExt.Floors.GetEnabled() {
+			return true
+		}
+	}
+	return false
+}
+
+// resolveFloors does selection of floors fields from request data and dynamic fetched data if dynamic fetch is enabled
+func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions) (*openrtb_ext.PriceFloorRules, []error) {
+	var errList []error
+	var floorRules *openrtb_ext.PriceFloorRules
+
+	reqFloor := extractFloorsFromRequest(bidRequestWrapper)
+	if reqFloor != nil {
+		floorRules, errList = createFloorsFrom(reqFloor, account, openrtb_ext.FetchNone, openrtb_ext.RequestLocation)
+	} else {
+		floorRules, errList = createFloorsFrom(nil, account, openrtb_ext.FetchNone, openrtb_ext.NoDataLocation)
+	}
+	return floorRules, errList
+}
+
+// createFloorsFrom does preparation of floors data which shall be used for further processing
+func createFloorsFrom(floors *openrtb_ext.PriceFloorRules, account config.Account, fetchStatus, floorLocation string) (*openrtb_ext.PriceFloorRules, []error) {
+	var floorModelErrList []error
+	finalFloors := &openrtb_ext.PriceFloorRules{
+		FetchStatus:        fetchStatus,
+		PriceFloorLocation: floorLocation,
+	}
+
+	if floors != nil {
+		floorValidationErr := validateFloorParams(floors)
+		if floorValidationErr != nil {
+			return finalFloors, append(floorModelErrList, floorValidationErr)
+		}
+
+		finalFloors.Enforcement = floors.Enforcement
+		if floors.Data != nil {
+			validModelGroups, floorModelErrList := selectValidFloorModelGroups(floors.Data.ModelGroups, account)
+			if len(validModelGroups) == 0 {
+				return finalFloors, floorModelErrList
+			} else {
+				*finalFloors = *floors
+				finalFloors.Data = new(openrtb_ext.PriceFloorData)
+				*finalFloors.Data = *floors.Data
+				finalFloors.PriceFloorLocation = floorLocation
+				finalFloors.FetchStatus = fetchStatus
+				if len(validModelGroups) > 1 {
+					validModelGroups = selectFloorModelGroup(validModelGroups, rand.Intn)
+				}
+				finalFloors.Data.ModelGroups = []openrtb_ext.PriceFloorModelGroup{validModelGroups[0].Copy()}
+			}
+		}
+	}
+
+	return finalFloors, floorModelErrList
+}
+
+// extractFloorsFromRequest gets floors data from req.ext.prebid.floors
+func extractFloorsFromRequest(bidRequestWrapper *openrtb_ext.RequestWrapper) *openrtb_ext.PriceFloorRules {
+	requestExt, err := bidRequestWrapper.GetRequestExt()
+	if err == nil {
+		prebidExt := requestExt.GetPrebid()
+		if prebidExt != nil && prebidExt.Floors != nil {
+			return prebidExt.Floors
+		}
+	}
+	return nil
+}
+
+// updateFloorsInRequest updates req.ext.prebid.floors with floors data
+func updateFloorsInRequest(bidRequestWrapper *openrtb_ext.RequestWrapper, priceFloors *openrtb_ext.PriceFloorRules) {
+	requestExt, err := bidRequestWrapper.GetRequestExt()
+	if err == nil {
+		prebidExt := requestExt.GetPrebid()
+		if prebidExt != nil {
+			prebidExt.Floors = priceFloors
+			requestExt.SetPrebid(prebidExt)
+			bidRequestWrapper.RebuildRequest()
+		}
+	}
+}
diff --git a/floors/rule.go b/floors/rule.go
new file mode 100644
index 00000000..52bb547f
--- /dev/null
+++ b/floors/rule.go
@@ -0,0 +1,554 @@
+package floors
+
+import (
+	"fmt"
+	"math/bits"
+	"regexp"
+	"sort"
+	"strings"
+
+	"github.com/golang/glog"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	SiteDomain          string = "siteDomain"
+	PubDomain           string = "pubDomain"
+	Domain              string = "domain"
+	Bundle              string = "bundle"
+	Channel             string = "channel"
+	MediaType           string = "mediaType"
+	Size                string = "size"
+	GptSlot             string = "gptSlot"
+	AdUnitCode          string = "adUnitCode"
+	Country             string = "country"
+	DeviceType          string = "deviceType"
+	Tablet              string = "tablet"
+	Desktop             string = "desktop"
+	Phone               string = "phone"
+	BannerMedia         string = "banner"
+	VideoMedia          string = "video"
+	VideoOutstreamMedia string = "video-outstream"
+	AudioMedia          string = "audio"
+	NativeMedia         string = "native"
+)
+
+// getFloorCurrency returns floors currency provided in floors JSON,
+// if currency is not provided then defaults to USD
+func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string {
+	var floorCur string
+
+	if floorExt != nil && floorExt.Data != nil {
+		if floorExt.Data.Currency != "" {
+			floorCur = floorExt.Data.Currency
+		}
+
+		if len(floorExt.Data.ModelGroups) > 0 && floorExt.Data.ModelGroups[0].Currency != "" {
+			floorCur = floorExt.Data.ModelGroups[0].Currency
+		}
+	}
+
+	if len(floorCur) == 0 {
+		floorCur = "USD"
+	}
+
+	return floorCur
+}
+
+// getMinFloorValue returns floorMin and floorMinCur,
+// values provided in impression extension are considered over floors JSON.
+func getMinFloorValue(floorExt *openrtb_ext.PriceFloorRules, imp *openrtb_ext.ImpWrapper, conversions currency.Conversions) (float64, string, error) {
+	var err error
+	var rate float64
+	var floorCur string
+	floorMin := roundToFourDecimals(floorExt.FloorMin)
+	floorMinCur := floorExt.FloorMinCur
+
+	impFloorMin, impFloorCur, err := getFloorMinAndCurFromImp(imp)
+	if err == nil {
+		if impFloorMin > 0.0 {
+			floorMin = impFloorMin
+		}
+		if impFloorCur != "" {
+			floorMinCur = impFloorCur
+		}
+
+		floorCur = getFloorCurrency(floorExt)
+		if floorMin > 0.0 && floorMinCur != "" {
+			if floorExt.FloorMinCur != "" && impFloorCur != "" && floorExt.FloorMinCur != impFloorCur {
+				glog.Warning("FloorMinCur are different in floorExt and ImpExt")
+			}
+			if floorCur != "" && floorMinCur != floorCur {
+				rate, err = conversions.GetRate(floorMinCur, floorCur)
+				floorMin = rate * floorMin
+			}
+		}
+		floorMin = roundToFourDecimals(floorMin)
+	}
+	if err != nil {
+		return floorMin, floorCur, fmt.Errorf("Error in getting FloorMin value : '%v'", err.Error())
+	} else {
+		return floorMin, floorCur, err
+	}
+}
+
+// getFloorMinAndCurFromImp returns floorMin and floorMinCur from impression extension
+func getFloorMinAndCurFromImp(imp *openrtb_ext.ImpWrapper) (float64, string, error) {
+	var floorMin float64
+	var floorMinCur string
+
+	impExt, err := imp.GetImpExt()
+	if impExt != nil {
+		impExtPrebid := impExt.GetPrebid()
+		if impExtPrebid != nil && impExtPrebid.Floors != nil {
+			if impExtPrebid.Floors.FloorMin > 0.0 {
+				floorMin = impExtPrebid.Floors.FloorMin
+			}
+
+			if impExtPrebid.Floors.FloorMinCur != "" {
+				floorMinCur = impExtPrebid.Floors.FloorMinCur
+			}
+		}
+	}
+	return floorMin, floorMinCur, err
+}
+
+// updateImpExtWithFloorDetails updates floors related details into imp.ext.prebid.floors
+func updateImpExtWithFloorDetails(imp *openrtb_ext.ImpWrapper, matchedRule string, floorRuleVal, floorVal float64) error {
+	impExt, err := imp.GetImpExt()
+	if err != nil {
+		return err
+	}
+	extImpPrebid := impExt.GetPrebid()
+	if extImpPrebid == nil {
+		extImpPrebid = &openrtb_ext.ExtImpPrebid{}
+	}
+	extImpPrebid.Floors = &openrtb_ext.ExtImpPrebidFloors{
+		FloorRule:      matchedRule,
+		FloorRuleValue: floorRuleVal,
+		FloorValue:     floorVal,
+	}
+	impExt.SetPrebid(extImpPrebid)
+	return err
+}
+
+// selectFloorModelGroup selects one modelgroup based on modelweight out of multiple modelgroups, if provided into floors JSON.
+func selectFloorModelGroup(modelGroups []openrtb_ext.PriceFloorModelGroup, f func(int) int) []openrtb_ext.PriceFloorModelGroup {
+	totalModelWeight := 0
+	for i := 0; i < len(modelGroups); i++ {
+		if modelGroups[i].ModelWeight == nil {
+			modelGroups[i].ModelWeight = new(int)
+			*modelGroups[i].ModelWeight = 1
+		}
+		totalModelWeight += *modelGroups[i].ModelWeight
+
+	}
+
+	sort.SliceStable(modelGroups, func(i, j int) bool {
+		if modelGroups[i].ModelWeight != nil && modelGroups[j].ModelWeight != nil {
+			return *modelGroups[i].ModelWeight < *modelGroups[j].ModelWeight
+		}
+		return false
+	})
+
+	winWeight := f(totalModelWeight + 1)
+	for i, modelGroup := range modelGroups {
+		winWeight -= *modelGroup.ModelWeight
+		if winWeight <= 0 {
+			modelGroups[0], modelGroups[i] = modelGroups[i], modelGroups[0]
+			return modelGroups[:1]
+		}
+
+	}
+	return modelGroups[:1]
+}
+
+// shouldSkipFloors returns flag to decide skipping of floors singalling based on skipRate provided
+func shouldSkipFloors(ModelGroupsSkipRate, DataSkipRate, RootSkipRate int, f func(int) int) bool {
+	skipRate := 0
+
+	if ModelGroupsSkipRate > 0 {
+		skipRate = ModelGroupsSkipRate
+	} else if DataSkipRate > 0 {
+		skipRate = DataSkipRate
+	} else {
+		skipRate = RootSkipRate
+	}
+
+	if skipRate == 0 {
+		return false
+	}
+	return skipRate >= f(skipRateMax+1)
+}
+
+// findRule prepares rule combinations based on schema dimensions provided in floors data, request values associated with these fields and
+// does matching with rules provided in floors data and returns matched rule
+func findRule(ruleValues map[string]float64, delimiter string, desiredRuleKey []string) (string, bool) {
+	ruleKeys := prepareRuleCombinations(desiredRuleKey, delimiter)
+	for i := 0; i < len(ruleKeys); i++ {
+		if _, ok := ruleValues[ruleKeys[i]]; ok {
+			return ruleKeys[i], true
+		}
+	}
+	return "", false
+}
+
+// createRuleKey prepares rule keys based on schema dimension and values present in request
+func createRuleKey(floorSchema openrtb_ext.PriceFloorSchema, request *openrtb_ext.RequestWrapper, imp *openrtb_ext.ImpWrapper) []string {
+	var ruleKeys []string
+
+	for _, field := range floorSchema.Fields {
+		value := catchAll
+		switch field {
+		case MediaType:
+			value = getMediaType(imp.Imp)
+		case Size:
+			value = getSizeValue(imp.Imp)
+		case Domain:
+			value = getDomain(request)
+		case SiteDomain:
+			value = getSiteDomain(request)
+		case Bundle:
+			value = getBundle(request)
+		case PubDomain:
+			value = getPublisherDomain(request)
+		case Country:
+			value = getDeviceCountry(request)
+		case DeviceType:
+			value = getDeviceType(request)
+		case Channel:
+			value = getChannelName(request)
+		case GptSlot:
+			value = getGptSlot(imp)
+		case AdUnitCode:
+			value = getAdUnitCode(imp)
+		}
+		ruleKeys = append(ruleKeys, value)
+	}
+	return ruleKeys
+}
+
+// getDeviceType returns device type provided into request
+func getDeviceType(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.Device == nil || len(request.Device.UA) == 0 {
+		return value
+	}
+	if isMobileDevice(request.Device.UA) {
+		value = Phone
+	} else if isTabletDevice(request.Device.UA) {
+		value = Tablet
+	} else {
+		value = Desktop
+	}
+	return value
+}
+
+// getDeviceCountry returns device country provided into request
+func getDeviceCountry(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.Device != nil && request.Device.Geo != nil {
+		value = request.Device.Geo.Country
+	}
+	return value
+}
+
+// getMediaType returns media type for give impression
+func getMediaType(imp *openrtb2.Imp) string {
+	value := catchAll
+	formatCount := 0
+
+	if imp.Banner != nil {
+		formatCount++
+		value = BannerMedia
+	}
+	if imp.Video != nil && imp.Video.Placement != 1 {
+		formatCount++
+		value = VideoOutstreamMedia
+	}
+	if imp.Video != nil && imp.Video.Placement == 1 {
+		formatCount++
+		value = VideoMedia
+	}
+	if imp.Audio != nil {
+		formatCount++
+		value = AudioMedia
+	}
+	if imp.Native != nil {
+		formatCount++
+		value = NativeMedia
+	}
+
+	if formatCount > 1 {
+		return catchAll
+	}
+	return value
+}
+
+// getSizeValue returns size for given media type in WxH format
+func getSizeValue(imp *openrtb2.Imp) string {
+	size := catchAll
+	width := int64(0)
+	height := int64(0)
+
+	if imp.Banner != nil {
+		width, height = getBannerSize(imp)
+	} else if imp.Video != nil {
+		width = imp.Video.W
+		height = imp.Video.H
+	}
+
+	if width != 0 && height != 0 {
+		size = fmt.Sprintf("%dx%d", width, height)
+	}
+	return size
+}
+
+// getBannerSize returns width and height for given banner impression
+func getBannerSize(imp *openrtb2.Imp) (int64, int64) {
+	width := int64(0)
+	height := int64(0)
+
+	if len(imp.Banner.Format) == 1 {
+		return imp.Banner.Format[0].W, imp.Banner.Format[0].H
+	} else if len(imp.Banner.Format) > 1 {
+		return width, height
+	} else if imp.Banner.W != nil && imp.Banner.H != nil {
+		width = *imp.Banner.W
+		height = *imp.Banner.H
+	}
+	return width, height
+}
+
+// getDomain returns domain provided into site or app object
+func getDomain(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.Site != nil {
+		if len(request.Site.Domain) > 0 {
+			value = request.Site.Domain
+		} else if request.Site.Publisher != nil && len(request.Site.Publisher.Domain) > 0 {
+			value = request.Site.Publisher.Domain
+		}
+	} else if request.App != nil {
+		if len(request.App.Domain) > 0 {
+			value = request.App.Domain
+		} else if request.App.Publisher != nil && len(request.App.Publisher.Domain) > 0 {
+			value = request.App.Publisher.Domain
+		}
+	}
+	return value
+}
+
+// getSiteDomain  returns domain provided into site object
+func getSiteDomain(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.Site != nil && len(request.Site.Domain) > 0 {
+		value = request.Site.Domain
+	} else if request.App != nil && len(request.App.Domain) > 0 {
+		value = request.App.Domain
+	}
+	return value
+}
+
+// getPublisherDomain returns publisher domain provided into site or app object
+func getPublisherDomain(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.Site != nil && request.Site.Publisher != nil && len(request.Site.Publisher.Domain) > 0 {
+		value = request.Site.Publisher.Domain
+	} else if request.App != nil && request.App.Publisher != nil && len(request.App.Publisher.Domain) > 0 {
+		value = request.App.Publisher.Domain
+	}
+	return value
+}
+
+// getBundle returns app bundle type
+func getBundle(request *openrtb_ext.RequestWrapper) string {
+	value := catchAll
+	if request.App != nil && len(request.App.Bundle) > 0 {
+		value = request.App.Bundle
+	}
+	return value
+}
+
+// getGptSlot returns gptSlot
+func getGptSlot(imp *openrtb_ext.ImpWrapper) string {
+	value := catchAll
+
+	impExt, err := imp.GetImpExt()
+	if err == nil {
+		extData := impExt.GetData()
+		if extData != nil {
+			if extData.AdServer != nil && extData.AdServer.Name == "gam" {
+				gptSlot := extData.AdServer.AdSlot
+				if gptSlot != "" {
+					value = gptSlot
+				}
+			} else if extData.PbAdslot != "" {
+				value = extData.PbAdslot
+			}
+		}
+	}
+	return value
+}
+
+// getChannelName returns channel name
+func getChannelName(bidRequest *openrtb_ext.RequestWrapper) string {
+	reqExt, err := bidRequest.GetRequestExt()
+	if err == nil && reqExt != nil {
+		prebidExt := reqExt.GetPrebid()
+		if prebidExt != nil && prebidExt.Channel != nil {
+			return prebidExt.Channel.Name
+		}
+	}
+	return catchAll
+}
+
+// getAdUnitCode returns adUnit code
+func getAdUnitCode(imp *openrtb_ext.ImpWrapper) string {
+	adUnitCode := catchAll
+
+	impExt, err := imp.GetImpExt()
+	if err == nil && impExt != nil && impExt.GetGpId() != "" {
+		return impExt.GetGpId()
+	}
+
+	if imp.TagID != "" {
+		return imp.TagID
+	}
+
+	if impExt != nil {
+		impExtData := impExt.GetData()
+		if impExtData != nil && impExtData.PbAdslot != "" {
+			return impExtData.PbAdslot
+		}
+
+		prebidExt := impExt.GetPrebid()
+		if prebidExt != nil && prebidExt.StoredRequest.ID != "" {
+			return prebidExt.StoredRequest.ID
+		}
+	}
+
+	return adUnitCode
+}
+
+// isMobileDevice returns true if device is mobile
+func isMobileDevice(userAgent string) bool {
+	isMobile, err := regexp.MatchString("(?i)Phone|iPhone|Android.*Mobile|Mobile.*Android", userAgent)
+	if err != nil {
+		return false
+	}
+	return isMobile
+}
+
+// isTabletDevice returns true if device is tablet
+func isTabletDevice(userAgent string) bool {
+	isTablet, err := regexp.MatchString("(?i)tablet|iPad|touch.*Windows NT|Windows NT.*touch|Android", userAgent)
+	if err != nil {
+		return false
+	}
+	return isTablet
+}
+
+// prepareRuleCombinations prepares rule combinations based on schema dimensions and request fields
+func prepareRuleCombinations(keys []string, delimiter string) []string {
+	var schemaFields []string
+
+	numSchemaFields := len(keys)
+	ruleKey := newFloorRuleKeys(delimiter)
+	for i := 0; i < numSchemaFields; i++ {
+		schemaFields = append(schemaFields, strings.ToLower(keys[i]))
+	}
+	ruleKey.appendRuleKey(schemaFields)
+
+	for numWildCard := 1; numWildCard <= numSchemaFields; numWildCard++ {
+		newComb := generateCombinations(numSchemaFields, numWildCard)
+		sortCombinations(newComb, numSchemaFields)
+
+		for i := 0; i < len(newComb); i++ {
+			eachSet := make([]string, numSchemaFields)
+			copy(eachSet, schemaFields)
+			for j := 0; j < len(newComb[i]); j++ {
+				eachSet[newComb[i][j]] = catchAll
+			}
+			ruleKey.appendRuleKey(eachSet)
+		}
+	}
+	return ruleKey.getAllRuleKeys()
+}
+
+// generateCombinations generates every permutation for the given number of fields with the specified number of
+// wildcards. Permutations are returned as a list of integer lists where each integer list represents a single
+// permutation with each integer indicating the position of the fields that are wildcards
+// source: https://docs.prebid.org/dev-docs/modules/floors.html#rule-selection-process
+func generateCombinations(numSchemaFields int, numWildCard int) (comb [][]int) {
+
+	for subsetBits := 1; subsetBits < (1 << numSchemaFields); subsetBits++ {
+		if bits.OnesCount(uint(subsetBits)) != numWildCard {
+			continue
+		}
+		var subset []int
+		for object := 0; object < numSchemaFields; object++ {
+			if (subsetBits>>object)&1 == 1 {
+				subset = append(subset, object)
+			}
+		}
+		comb = append(comb, subset)
+	}
+	return comb
+}
+
+// sortCombinations sorts the list of combinations from most specific to least specific. A combination is considered more specific than
+// another combination if it has more exact values (less wildcards). If two combinations have the same number of wildcards, a combination
+// is considered more specific than another if its left-most fields are more exact.
+func sortCombinations(comb [][]int, numSchemaFields int) {
+	totalComb := 1 << numSchemaFields
+
+	sort.SliceStable(comb, func(i, j int) bool {
+		wt1 := 0
+		for k := 0; k < len(comb[i]); k++ {
+			wt1 += 1 << (totalComb - comb[i][k])
+		}
+
+		wt2 := 0
+		for k := 0; k < len(comb[j]); k++ {
+			wt2 += 1 << (totalComb - comb[j][k])
+		}
+		return wt1 < wt2
+	})
+}
+
+// ruleKeys defines struct used for maintaining rule combinations generated from schema fields and reqeust values.
+type ruleKeys struct {
+	keyMap    map[string]bool
+	keys      []string
+	delimiter string
+}
+
+// newFloorRuleKeys allocates and initialise ruleKeys
+func newFloorRuleKeys(delimiter string) *ruleKeys {
+	rulekey := new(ruleKeys)
+	rulekey.delimiter = delimiter
+	rulekey.keyMap = map[string]bool{}
+	return rulekey
+}
+
+// appendRuleKey appends unique rules keys into ruleKeys array
+func (r *ruleKeys) appendRuleKey(rawKey []string) {
+	var key string
+	key = rawKey[0]
+	for j := 1; j < len(rawKey); j++ {
+		key += r.delimiter + rawKey[j]
+	}
+
+	if _, found := r.keyMap[key]; !found {
+		r.keyMap[key] = true
+		r.keys = append(r.keys, key)
+	}
+}
+
+// getAllRuleKeys returns all the rules prepared
+func (r *ruleKeys) getAllRuleKeys() []string {
+	return r.keys
+}
diff --git a/floors/validate.go b/floors/validate.go
new file mode 100644
index 00000000..5624735c
--- /dev/null
+++ b/floors/validate.go
@@ -0,0 +1,119 @@
+package floors
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validSchemaDimensions = map[string]struct{}{
+	SiteDomain: {},
+	PubDomain:  {},
+	Domain:     {},
+	Bundle:     {},
+	Channel:    {},
+	MediaType:  {},
+	Size:       {},
+	GptSlot:    {},
+	AdUnitCode: {},
+	Country:    {},
+	DeviceType: {},
+}
+
+// validateSchemaDimensions validates schema dimesions given in floors JSON
+func validateSchemaDimensions(fields []string) error {
+	for i := range fields {
+		if _, isPresent := validSchemaDimensions[fields[i]]; !isPresent {
+			return fmt.Errorf("Invalid schema dimension provided = '%s' in Schema Fields = '%v'", fields[i], fields)
+		}
+	}
+	return nil
+}
+
+// validateFloorRulesAndLowerValidRuleKey validates rule keys for number of schema dimension fields and drops invalid rules.
+// It also lower case of rule if any charactor in a rule is upper
+func validateFloorRulesAndLowerValidRuleKey(schema openrtb_ext.PriceFloorSchema, delimiter string, ruleValues map[string]float64) []error {
+	var errs []error
+	for key, val := range ruleValues {
+		parsedKey := strings.Split(key, delimiter)
+		if len(parsedKey) != len(schema.Fields) {
+			// Number of fields in rule and number of schema fields are not matching
+			errs = append(errs, fmt.Errorf("Invalid Floor Rule = '%s' for Schema Fields = '%v'", key, schema.Fields))
+			delete(ruleValues, key)
+			continue
+		}
+		lowerKey := strings.ToLower(key)
+		if strings.Compare(key, lowerKey) != 0 {
+			delete(ruleValues, key)
+			ruleValues[lowerKey] = val
+		}
+	}
+	return errs
+}
+
+// validateFloorParams validates SchemaVersion, SkipRate and FloorMin
+func validateFloorParams(extFloorRules *openrtb_ext.PriceFloorRules) error {
+	if extFloorRules.Data != nil && len(extFloorRules.Data.FloorsSchemaVersion) > 0 && extFloorRules.Data.FloorsSchemaVersion != "2" {
+		return fmt.Errorf("Invalid FloorsSchemaVersion = '%v', supported version 2", extFloorRules.Data.FloorsSchemaVersion)
+	}
+
+	if extFloorRules.Data != nil && (extFloorRules.Data.SkipRate < skipRateMin || extFloorRules.Data.SkipRate > skipRateMax) {
+		return fmt.Errorf("Invalid SkipRate = '%v' at ext.prebid.floors.data.skiprate", extFloorRules.Data.SkipRate)
+	}
+
+	if extFloorRules.SkipRate < skipRateMin || extFloorRules.SkipRate > skipRateMax {
+		return fmt.Errorf("Invalid SkipRate = '%v' at ext.prebid.floors.skiprate", extFloorRules.SkipRate)
+	}
+
+	if extFloorRules.FloorMin < 0.0 {
+		return fmt.Errorf("Invalid FloorMin = '%v', value should be >= 0", extFloorRules.FloorMin)
+	}
+
+	return nil
+}
+
+// selectValidFloorModelGroups validates each modelgroup for SkipRate and ModelGroup and drops invalid modelGroups
+func selectValidFloorModelGroups(modelGroups []openrtb_ext.PriceFloorModelGroup, account config.Account) ([]openrtb_ext.PriceFloorModelGroup, []error) {
+	var errs []error
+	var validModelGroups []openrtb_ext.PriceFloorModelGroup
+	if len(modelGroups) == 0 {
+		return validModelGroups, []error{fmt.Errorf("No model group present in floors.data")}
+	}
+
+	for _, modelGroup := range modelGroups {
+		if err := validateSchemaDimensions(modelGroup.Schema.Fields); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		if account.PriceFloors.MaxSchemaDims > 0 && len(modelGroup.Schema.Fields) > account.PriceFloors.MaxSchemaDims {
+			errs = append(errs, fmt.Errorf("Invalid Floor Model = '%v' due to number of schema fields = '%v' are greater than limit %v", modelGroup.ModelVersion, len(modelGroup.Schema.Fields), account.PriceFloors.MaxSchemaDims))
+			continue
+		}
+
+		if account.PriceFloors.MaxRule > 0 && len(modelGroup.Values) > account.PriceFloors.MaxRule {
+			errs = append(errs, fmt.Errorf("Invalid Floor Model = '%v' due to number of rules = '%v' are greater than limit %v", modelGroup.ModelVersion, len(modelGroup.Values), account.PriceFloors.MaxRule))
+			continue
+		}
+
+		if modelGroup.SkipRate < skipRateMin || modelGroup.SkipRate > skipRateMax {
+			errs = append(errs, fmt.Errorf("Invalid Floor Model = '%v' due to SkipRate = '%v' is out of range (1-100)", modelGroup.ModelVersion, modelGroup.SkipRate))
+			continue
+		}
+
+		if modelGroup.ModelWeight != nil && (*modelGroup.ModelWeight < modelWeightMin || *modelGroup.ModelWeight > modelWeightMax) {
+			errs = append(errs, fmt.Errorf("Invalid Floor Model = '%v' due to ModelWeight = '%v' is out of range (1-100)", modelGroup.ModelVersion, *modelGroup.ModelWeight))
+			continue
+		}
+
+		if modelGroup.Default < 0.0 {
+			errs = append(errs, fmt.Errorf("Invalid Floor Model = '%v' due to Default = '%v' is less than 0", modelGroup.ModelVersion, modelGroup.Default))
+			continue
+		}
+
+		validModelGroups = append(validModelGroups, modelGroup)
+	}
+	return validModelGroups, errs
+}
diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go
index 6d984fc4..9a1fd341 100644
--- a/hooks/hookexecution/executor.go
+++ b/hooks/hookexecution/executor.go
@@ -314,33 +314,33 @@ func (e *hookExecutor) pushStageOutcome(outcome StageOutcome) {
 
 type EmptyHookExecutor struct{}
 
-func (executor *EmptyHookExecutor) SetAccount(_ *config.Account) {}
+func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {}
 
-func (executor *EmptyHookExecutor) GetOutcomes() []StageOutcome {
+func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome {
 	return []StageOutcome{}
 }
 
-func (executor *EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) {
+func (executor EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) {
 	return body, nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) {
+func (executor EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) {
 	return body, nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.RequestWrapper) error {
+func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.RequestWrapper) error {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError {
+func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
+func (executor EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
+func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
 }
 
-func (executor *EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}
+func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}
diff --git a/macros/macros.go b/macros/macros.go
index b265e4cd..7292e0c8 100644
--- a/macros/macros.go
+++ b/macros/macros.go
@@ -14,6 +14,7 @@ type EndpointTemplateParams struct {
 	AccountID   string
 	AdUnit      string
 	MediaType   string
+	GvlID       string
 }
 
 // UserSyncTemplateParams specifies params for an user sync URL template
diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go
new file mode 100644
index 00000000..409f9de9
--- /dev/null
+++ b/openrtb_ext/floors.go
@@ -0,0 +1,114 @@
+package openrtb_ext
+
+// Defines strings for FetchStatus
+const (
+	FetchSuccess    = "success"
+	FetchTimeout    = "timeout"
+	FetchError      = "error"
+	FetchInprogress = "inprogress"
+	FetchNone       = "none"
+)
+
+// Defines strings for PriceFloorLocation
+const (
+	NoDataLocation  = "noData"
+	RequestLocation = "request"
+	FetchLocation   = "fetch"
+)
+
+// PriceFloorRules defines the contract for bidrequest.ext.prebid.floors
+type PriceFloorRules struct {
+	FloorMin           float64                `json:"floormin,omitempty"`
+	FloorMinCur        string                 `json:"floormincur,omitempty"`
+	SkipRate           int                    `json:"skiprate,omitempty"`
+	Location           *PriceFloorEndpoint    `json:"floorendpoint,omitempty"`
+	Data               *PriceFloorData        `json:"data,omitempty"`
+	Enforcement        *PriceFloorEnforcement `json:"enforcement,omitempty"`
+	Enabled            *bool                  `json:"enabled,omitempty"`
+	Skipped            *bool                  `json:"skipped,omitempty"`
+	FloorProvider      string                 `json:"floorprovider,omitempty"`
+	FetchStatus        string                 `json:"fetchstatus,omitempty"`
+	PriceFloorLocation string                 `json:"location,omitempty"`
+}
+
+type PriceFloorEndpoint struct {
+	URL string `json:"url,omitempty"`
+}
+
+type PriceFloorData struct {
+	Currency            string                 `json:"currency,omitempty"`
+	SkipRate            int                    `json:"skiprate,omitempty"`
+	FloorsSchemaVersion string                 `json:"floorsschemaversion,omitempty"`
+	ModelTimestamp      int                    `json:"modeltimestamp,omitempty"`
+	ModelGroups         []PriceFloorModelGroup `json:"modelgroups,omitempty"`
+	FloorProvider       string                 `json:"floorprovider,omitempty"`
+}
+
+type PriceFloorModelGroup struct {
+	Currency     string             `json:"currency,omitempty"`
+	ModelWeight  *int               `json:"modelweight,omitempty"`
+	ModelVersion string             `json:"modelversion,omitempty"`
+	SkipRate     int                `json:"skiprate,omitempty"`
+	Schema       PriceFloorSchema   `json:"schema,omitempty"`
+	Values       map[string]float64 `json:"values,omitempty"`
+	Default      float64            `json:"default,omitempty"`
+}
+type PriceFloorSchema struct {
+	Fields    []string `json:"fields,omitempty"`
+	Delimiter string   `json:"delimiter,omitempty"`
+}
+
+type PriceFloorEnforcement struct {
+	EnforceJS     *bool `json:"enforcejs,omitempty"`
+	EnforcePBS    *bool `json:"enforcepbs,omitempty"`
+	FloorDeals    *bool `json:"floordeals,omitempty"`
+	BidAdjustment *bool `json:"bidadjustment,omitempty"`
+	EnforceRate   int   `json:"enforcerate,omitempty"`
+}
+
+type ImpFloorExt struct {
+	FloorRule      string  `json:"floorrule,omitempty"`
+	FloorRuleValue float64 `json:"floorrulevalue,omitempty"`
+	FloorValue     float64 `json:"floorvalue,omitempty"`
+}
+type Price struct {
+	FloorMin    float64 `json:"floormin,omitempty"`
+	FloorMinCur string  `json:"floormincur,omitempty"`
+}
+
+type ExtImp struct {
+	Prebid *ImpExtPrebid `json:"prebid,omitempty"`
+}
+
+type ImpExtPrebid struct {
+	Floors Price `json:"floors,omitempty"`
+}
+
+// GetEnabled will check if floors is enabled in request
+func (Floors *PriceFloorRules) GetEnabled() bool {
+	if Floors != nil && Floors.Enabled != nil {
+		return *Floors.Enabled
+	}
+	return true
+}
+
+func (modelGroup PriceFloorModelGroup) Copy() PriceFloorModelGroup {
+	newModelGroup := new(PriceFloorModelGroup)
+	newModelGroup.Currency = modelGroup.Currency
+	newModelGroup.ModelVersion = modelGroup.ModelVersion
+	newModelGroup.SkipRate = modelGroup.SkipRate
+	newModelGroup.Default = modelGroup.Default
+	if modelGroup.ModelWeight != nil {
+		newModelGroup.ModelWeight = new(int)
+		*newModelGroup.ModelWeight = *modelGroup.ModelWeight
+	}
+
+	newModelGroup.Schema.Delimiter = modelGroup.Schema.Delimiter
+	newModelGroup.Schema.Fields = make([]string, len(modelGroup.Schema.Fields))
+	copy(newModelGroup.Schema.Fields, modelGroup.Schema.Fields)
+	newModelGroup.Values = make(map[string]float64, len(modelGroup.Values))
+	for key, val := range modelGroup.Values {
+		newModelGroup.Values[key] = val
+	}
+	return *newModelGroup
+}
diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go
index e0fdd2d7..d0c07ef4 100644
--- a/openrtb_ext/imp.go
+++ b/openrtb_ext/imp.go
@@ -42,6 +42,26 @@ type ExtImpPrebid struct {
 	Options *Options `json:"options,omitempty"`
 
 	Passthrough json.RawMessage `json:"passthrough,omitempty"`
+
+	Floors *ExtImpPrebidFloors `json:"floors,omitempty"`
+}
+
+type ExtImpDataAdServer struct {
+	Name   string `json:"name"`
+	AdSlot string `json:"adslot"`
+}
+
+type ExtImpData struct {
+	PbAdslot string              `json:"pbadslot,omitempty"`
+	AdServer *ExtImpDataAdServer `json:"adserver,omitempty"`
+}
+
+type ExtImpPrebidFloors struct {
+	FloorRule      string  `json:"floorrule,omitempty"`
+	FloorRuleValue float64 `json:"floorrulevalue,omitempty"`
+	FloorValue     float64 `json:"floorvalue,omitempty"`
+	FloorMin       float64 `json:"floormin,omitempty"`
+	FloorMinCur    string  `json:"floorminCur,omitempty"`
 }
 
 // ExtStoredRequest defines the contract for bidrequest.imp[i].ext.prebid.storedrequest
diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go
index 5f76aed9..7264be13 100644
--- a/openrtb_ext/imp_rubicon.go
+++ b/openrtb_ext/imp_rubicon.go
@@ -15,6 +15,7 @@ type ExtImpRubicon struct {
 	Visitor          json.RawMessage    `json:"visitor,omitempty"`
 	Video            rubiconVideoParams `json:"video"`
 	Debug            impExtRubiconDebug `json:"debug,omitempty"`
+	PChain           string             `json:"pchain,omitempty"`
 }
 
 // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon.video
diff --git a/openrtb_ext/imp_taboola.go b/openrtb_ext/imp_taboola.go
index 40057a0a..798ee28c 100644
--- a/openrtb_ext/imp_taboola.go
+++ b/openrtb_ext/imp_taboola.go
@@ -5,6 +5,7 @@ type ImpExtTaboola struct {
 	PublisherDomain string   `json:"publisherDomain"`
 	BidFloor        float64  `json:"bidfloor"`
 	TagId           string   `json:"tagid"`
+	TagID           string   `json:"tagId"`
 	BCat            []string `json:"bcat"`
 	BAdv            []string `json:"badv"`
 	PageType        string   `json:"pageType"`
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 95c3e389..cf781f1c 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -67,6 +67,8 @@ type ExtRequestPrebid struct {
 	//AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request
 	AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"`
 
+	Floors      *PriceFloorRules       `json:"floors,omitempty"`
+	MultiBidMap map[string]ExtMultiBid `json:"-"`
 	// Trace controls the level of detail in the output information returned from executing hooks.
 	// There are two options:
 	// - verbose: sets maximum level of output information
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index aa301803..fee1fe97 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -51,6 +51,7 @@ const (
 	eidsKey                             = "eids"
 	gdprKey                             = "gdpr"
 	prebidKey                           = "prebid"
+	dataKey                             = "data"
 	schainKey                           = "schain"
 	us_privacyKey                       = "us_privacy"
 )
@@ -1272,8 +1273,10 @@ type ImpExt struct {
 	ext         map[string]json.RawMessage
 	extDirty    bool
 	prebid      *ExtImpPrebid
+	data        *ExtImpData
 	prebidDirty bool
 	tid         string
+	gpId        string
 	tidDirty    bool
 }
 
@@ -1300,6 +1303,14 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error {
 		}
 	}
 
+	dataJson, hasData := e.ext[dataKey]
+	if hasData {
+		e.data = &ExtImpData{}
+		if err := json.Unmarshal(dataJson, e.data); err != nil {
+			return err
+		}
+	}
+
 	tidJson, hasTid := e.ext["tid"]
 	if hasTid {
 		if err := json.Unmarshal(tidJson, &e.tid); err != nil {
@@ -1307,6 +1318,13 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error {
 		}
 	}
 
+	gpIdJson, hasGpId := e.ext["gpid"]
+	if hasGpId {
+		if err := json.Unmarshal(gpIdJson, &e.gpId); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -1385,6 +1403,14 @@ func (e *ImpExt) SetPrebid(prebid *ExtImpPrebid) {
 	e.prebidDirty = true
 }
 
+func (e *ImpExt) GetData() *ExtImpData {
+	if e.data == nil {
+		return nil
+	}
+	data := *e.data
+	return &data
+}
+
 func (e *ImpExt) GetTid() string {
 	tid := e.tid
 	return tid
@@ -1395,6 +1421,11 @@ func (e *ImpExt) SetTid(tid string) {
 	e.tidDirty = true
 }
 
+func (e *ImpExt) GetGpId() string {
+	gpId := e.gpId
+	return gpId
+}
+
 func CreateImpExtForTesting(ext map[string]json.RawMessage, prebid *ExtImpPrebid) ImpExt {
 	return ImpExt{ext: ext, prebid: prebid}
 }
diff --git a/static/bidder-info/adsinteractive.yaml b/static/bidder-info/adsinteractive.yaml
index 20a64bf2..e70c15b6 100644
--- a/static/bidder-info/adsinteractive.yaml
+++ b/static/bidder-info/adsinteractive.yaml
@@ -8,4 +8,9 @@ capabilities:
       - banner
   site:
     mediaTypes:
-      - banner
\ No newline at end of file
+      - banner
+userSync:
+  redirect:
+    url: "http://pb.adsinteractive.com/getuid?{{.RedirectURL}}"
+    userMacro: "$AUID"
+ 
\ No newline at end of file
diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml
index d1ddae74..3342b884 100644
--- a/static/bidder-info/adyoulike.yaml
+++ b/static/bidder-info/adyoulike.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4"
+endpoint: "https://hb-ss.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4"
 maintainer:
   email: "core@adyoulike.com"
 gvlVendorID: 259
diff --git a/static/bidder-info/madvertise.yaml b/static/bidder-info/madvertise.yaml
index 262987a6..f94f988a 100644
--- a/static/bidder-info/madvertise.yaml
+++ b/static/bidder-info/madvertise.yaml
@@ -1,6 +1,6 @@
 endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"
 maintainer:
-  email: "support@madvertise.com"
+  email: "hb@azerion.com"
 gvlVendorID: 153
 capabilities:
   app:
diff --git a/static/bidder-info/taboola.yaml b/static/bidder-info/taboola.yaml
index 693ef048..348165a9 100644
--- a/static/bidder-info/taboola.yaml
+++ b/static/bidder-info/taboola.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction/{{.Host}}/{{.PublisherID}}"
+endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction/{{.GvlID}}/{{.PublisherID}}"
 maintainer:
   email:  ps-team@taboola.com
 gvlVendorID: 42
diff --git a/static/bidder-params/taboola.json b/static/bidder-params/taboola.json
index 84bdf149..25c12660 100644
--- a/static/bidder-params/taboola.json
+++ b/static/bidder-params/taboola.json
@@ -12,7 +12,12 @@
       "type": "string"
     },
     "tagid": {
-      "type": "string"
+      "type": "string",
+      "description": "Deprecated, use tagId instead."
+    },
+    "tagId": {
+      "type": "string",
+      "description": "preferred, will get precedence if both tagId and tagid are defined"
     },
     "bidfloor": {
       "type": "number"
@@ -36,5 +41,12 @@
       "type": "integer"
     }
   },
-  "required": ["tagid","publisherId"]
+  "oneOf": [
+    {
+      "required": [ "tagid", "publisherId" ]
+    },
+    {
+      "required": [ "tagId", "publisherId" ]
+    }
+  ]
 }
diff --git a/util/randomutil/randomutil.go b/util/randomutil/randomutil.go
new file mode 100644
index 00000000..544eb1ab
--- /dev/null
+++ b/util/randomutil/randomutil.go
@@ -0,0 +1,15 @@
+package randomutil
+
+import (
+	"math/rand"
+)
+
+type RandomGenerator interface {
+	GenerateInt63() int64
+}
+
+type RandomNumberGenerator struct{}
+
+func (RandomNumberGenerator) GenerateInt63() int64 {
+	return rand.Int63()
+}


Test patch:
diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go
index 000d07e9..6797f4bb 100644
--- a/adapters/appnexus/appnexus_test.go
+++ b/adapters/appnexus/appnexus_test.go
@@ -16,6 +16,9 @@ func TestJsonSamples(t *testing.T) {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
 	}
 
+	bidderAppNexus, _ := bidder.(*adapter)
+	bidderAppNexus.randomGenerator = FakeRandomNumberGenerator{Number: 10}
+
 	adapterstest.RunJSONBidderTest(t, "appnexustest", bidder)
 }
 
@@ -26,3 +29,12 @@ func TestMemberQueryParam(t *testing.T) {
 		t.Errorf("appendMemberId() failed on URI with query string. Expected %s, got %s", expected, uriWithMember)
 	}
 }
+
+// fakerandomNumberGenerator
+type FakeRandomNumberGenerator struct {
+	Number int64
+}
+
+func (f FakeRandomNumberGenerator) GenerateInt63() int64 {
+	return f.Number
+}
diff --git a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
index 5a453979..86fa14aa 100644
--- a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
+++ b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
@@ -47,7 +47,7 @@
 			"id": "test-request-id",
 			"ext": {
 				"appnexus": {
-				 "adpod_id": "5577006791947779410",
+				 "adpod_id": "10",
 				  "hb_source": 6
 				},
 				"prebid": {}
diff --git a/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
new file mode 100644
index 00000000..2f5dee2d
--- /dev/null
+++ b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
@@ -0,0 +1,447 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    },
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "site": {
+      "pagecat": [
+        "val1",
+        "val2"
+      ],
+      "content": {
+        "data": [
+          {
+            "ext": {
+              "segtax": 1
+            },
+            "segment": [
+              {
+                "id": "segmentId1"
+              },
+              {
+                "id": "segmentId2"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": "1"
+            },
+            "segment": [
+              {
+                "id": "shouldNotBeCopied"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": 2
+            },
+            "segment": [
+              {
+                "id": "segmentId3"
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "user": {
+      "yob": 2000,
+      "geo": {
+        "country": "USA",
+        "lat": 47.627500,
+        "lon": -122.346200
+      },
+      "gender": "f",
+      "consent": "consent",
+      "data": [
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "someValue"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "4"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": [
+              4
+            ]
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied3"
+            }
+          ]
+        }
+      ]
+    },
+    "source": {
+      "schain": {
+        "complete": 0,
+        "nodes": [
+          {
+            "asi": "asi",
+            "sid": "sid"
+          }
+        ],
+        "ver": "1.0"
+      }
+    },
+    "regs": {
+      "gdpr": 1,
+      "us_privacy": "us_privacy"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "instl": 1,
+        "banner": {
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "data": {
+            "pbadslot": "pbadslot"
+          },
+          "bidder": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510,
+            "pchain": "pchain"
+          },
+          "skadn": {
+            "version": "1.0",
+            "sourceapp": "000",
+            "skadnetlist": {
+              "max": 0,
+              "excl": [
+                1
+              ],
+              "addl": [
+                "x.skadnetwork"
+              ]
+            }
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "uri?tk_xint=pbs-test-tracker",
+        "body": {
+          "id": "test-request-id",
+          "device": {
+            "ext": {
+              "rp": {
+                "pixelratio": 0
+              }
+            },
+            "ip": "123.123.123.123",
+            "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          },
+          "site": {
+            "pagecat": [
+              "val1",
+              "val2"
+            ],
+            "content": {
+              "data": [
+                {
+                  "ext": {
+                    "segtax": 1
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId1"
+                    },
+                    {
+                      "id": "segmentId2"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": "1"
+                  },
+                  "segment": [
+                    {
+                      "id": "shouldNotBeCopied"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": 2
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId3"
+                    }
+                  ]
+                }
+              ]
+            },
+            "ext": {
+              "rp": {
+                "site_id": 113932,
+                "target": {
+                  "iab": [
+                    "segmentId1",
+                    "segmentId2",
+                    "segmentId3"
+                  ]
+                }
+              }
+            },
+            "publisher": {
+              "ext": {
+                "rp": {
+                  "account_id": 1001
+                }
+              }
+            }
+          },
+          "user": {
+            "data": [
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "someValue"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "4"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": [
+                    4
+                  ]
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied3"
+                  }
+                ]
+              }
+            ],
+            "ext": {
+              "consent": "consent",
+              "rp": {
+                "target": {
+                  "iab": [
+                    "idToCopy",
+                    "idToCopy2"
+                  ]
+                }
+              }
+            }
+          },
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "source": {
+            "pchain": "pchain",
+            "ext": {
+              "schain": {
+                "complete": 0,
+                "nodes": [
+                  {
+                    "asi": "asi",
+                    "sid": "sid"
+                  }
+                ],
+                "ver": "1.0"
+              }
+            }
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 1,
+              "us_privacy": "us_privacy"
+            }
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "instl": 1,
+              "secure": 1,
+              "banner": {
+                "ext": {
+                  "rp": {
+                    "mime": "text/html"
+                  }
+                },
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "rp": {
+                  "target": {
+                    "pbadslot": "pbadslot",
+                    "pagecat": [
+                      "val1",
+                      "val2"
+                    ]
+                  },
+                  "track": {
+                    "mint": "",
+                    "mint_version": ""
+                  },
+                  "zone_id": 535510
+                },
+                "skadn": {
+                  "skadnetlist": {
+                    "max": 0,
+                    "excl": [
+                      1
+                    ],
+                    "addl": [
+                      "x.skadnetwork"
+                    ]
+                  },
+                  "sourceapp": "000",
+                  "version": "1.0"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "buyer": "123",
+              "bid": [
+                {
+                  "id": "test_bid_id",
+                  "impid": "test-imp-id",
+                  "price": 0.27543,
+                  "adm": "some-test-ad",
+                  "cid": "test_cid",
+                  "crid": "test_crid",
+                  "dealid": "test_dealid",
+                  "ext": {
+                    "prebid": {
+                      "meta": {
+                        "networkId": 123
+                      },
+                      "type": "banner"
+                    }
+                  }
+                }
+              ],
+              "seat": "adman"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "some-test-ad",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "ext": {
+              "prebid": {
+                "meta": {
+                  "networkId": 123
+                },
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-banner.json b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
index da4ec6a2..83073f0c 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-banner.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
@@ -60,6 +60,7 @@
         "lon": -122.346200
       },
       "gender": "f",
+      "consent": "consent",
       "data": [
         {
           "ext": {
@@ -130,7 +131,21 @@
           "bidder": {
             "accountId": 1001,
             "siteId": 113932,
-            "zoneId": 535510
+            "zoneId": 535510,
+            "pchain": "pchain"
+          },
+          "skadn": {
+            "version": "1.0",
+            "sourceapp": "000",
+            "skadnetlist": {
+              "max": 0,
+              "excl": [
+                1
+              ],
+              "addl": [
+                "x.skadnetwork"
+              ]
+            }
           }
         }
       }
@@ -269,6 +284,7 @@
               }
             ],
             "ext": {
+              "consent": "consent",
               "rp": {
                 "target": {
                   "iab": [
@@ -283,6 +299,9 @@
             "id": "1",
             "bundle": "com.wls.testwlsapplication"
           },
+          "source": {
+            "pchain": "pchain"
+          },
           "imp": [
             {
               "id": "test-imp-id",
@@ -311,6 +330,19 @@
                     "mint_version": ""
                   },
                   "zone_id": 535510
+                },
+                "skadn": {
+                  "skadnetlist": {
+                    "max": 0,
+                    "excl": [
+                      1
+                    ],
+                    "addl": [
+                      "x.skadnetwork"
+                    ]
+                  },
+                  "sourceapp": "000",
+                  "version": "1.0"
                 }
               }
             }
diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go
index 25b6a75d..51a9833c 100644
--- a/adapters/taboola/params_test.go
+++ b/adapters/taboola/params_test.go
@@ -34,6 +34,7 @@ func TestInvalidParams(t *testing.T) {
 
 var validParams = []string{
 	`{"publisherId" : "1", "tagid": "tag-id-for-example"}`,
+	`{"publisherId" : "1", "tagId": "tag-id-for-example"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":1}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","pageType":"pageType"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
@@ -41,14 +42,14 @@ var validParams = []string{
 
 var invalidParams = []string{
 	`{}`,
-	`{"publisherId" : "1"}`,
+	`{"tagId" : "1"}`,
+	`{"publisherId" : "1", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"publisherId" : 1, "tagid": "tag-id-for-example"}`,
 	`{"publisherId" : "1"", "tagid": 2}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "badv":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bidfloor":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "publisherDomain":1}`,
-	`{"publisherId" : "1", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":null}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":"1"}`,
diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go
index b5e87f08..320d08da 100644
--- a/adapters/taboola/taboola_test.go
+++ b/adapters/taboola/taboola_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderTaboola, config.Adapter{
-		Endpoint: "http://{{.MediaType}}.whatever.com/{{.Host}}/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint: "http://{{.MediaType}}.whatever.com/{{.GvlID}}/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 12, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -32,5 +32,5 @@ func TestEmptyExternalUrl(t *testing.T) {
 
 	bidderTaboola := bidder.(*adapter)
 
-	assert.Equal(t, "", bidderTaboola.hostName)
+	assert.Equal(t, "", bidderTaboola.gvlID)
 }
diff --git a/adapters/taboola/taboolatest/exemplary/banner.json b/adapters/taboola/taboolatest/exemplary/banner.json
index a3f819b8..e550c66e 100644
--- a/adapters/taboola/taboolatest/exemplary/banner.json
+++ b/adapters/taboola/taboolatest/exemplary/banner.json
@@ -23,7 +23,8 @@
         "ext": {
           "bidder": {
             "publisherId": "publisher-id",
-            "tagid": "tag-id"
+            "tagid": "tag-id",
+            "tagId": "tag-Id"
           }
         }
       }
@@ -42,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
@@ -64,11 +65,12 @@
                   }
                 ]
               },
-              "tagid" : "tag-id",
+              "tagid" : "tag-Id",
               "ext": {
                 "bidder": {
                   "publisherId": "publisher-id",
-                  "tagid": "tag-id"
+                  "tagid": "tag-id",
+                  "tagId": "tag-Id"
                 }
               }
             }
diff --git a/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json
new file mode 100644
index 00000000..830af06d
--- /dev/null
+++ b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json
@@ -0,0 +1,154 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            },
+            {
+              "w": 160,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "publisher-id",
+            "tagid": "tag-id",
+            "tagId": "tag-Id"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "http://domain.com",
+      "page": "http://page-domain.com",
+      "ref": "http://page-domain.com"
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+      "h": 300,
+      "w": 300
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://display.whatever.com/12/publisher-id",
+        "body": {
+          "id": "request-id",
+          "imp": [
+            {
+              "id": "impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  },
+                  {
+                    "w": 160,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid" : "tag-Id",
+              "ext": {
+                "bidder": {
+                  "publisherId": "publisher-id",
+                  "tagid": "tag-id",
+                  "tagId": "tag-Id"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "publisher-id",
+            "name": "publisher-id",
+            "domain": "http://domain.com",
+            "page": "http://page-domain.com",
+            "ref": "http://page-domain.com",
+            "publisher": {
+              "id": "publisher-id"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+            "h": 300,
+            "w": 300
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "123",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "request-id",
+                  "impid": "impression-id",
+                  "price": 2.1,
+                  "adid": "1",
+                  "adm": "<hrml></html>&wp=${AUCTION_PRICE}",
+                  "adomain": [
+                    "adomain.com"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "w": 300,
+                  "h": 250,
+                  "exp": 60,
+                  "lurl": "https://lurl.domain.com/"
+                }
+              ],
+              "seat": "1111"
+            }
+          ],
+          "bidid": "123",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "request-id",
+            "impid": "impression-id",
+            "price": 2.1,
+            "adid": "1",
+            "adm": "<hrml></html>&wp=2.1",
+            "adomain": [
+              "adomain.com"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "w": 300,
+            "h": 250,
+            "exp": 60,
+            "lurl": "https://lurl.domain.com/"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
index 7ba02f4e..9d603b8a 100644
--- a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
+++ b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
@@ -80,7 +80,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://native.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://native.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
@@ -150,7 +150,7 @@
     },
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/native.json b/adapters/taboola/taboolatest/exemplary/native.json
index 0d2c0417..88863fdf 100644
--- a/adapters/taboola/taboolatest/exemplary/native.json
+++ b/adapters/taboola/taboolatest/exemplary/native.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://native.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://native.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json
new file mode 100644
index 00000000..03f05db5
--- /dev/null
+++ b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json
@@ -0,0 +1,125 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "native": {
+          "request": "\"{\\\"ver\\\":\\\"1.1\\\",\\\"plcmttype\\\": 4,\\\"plcmtcnt\\\": 1,\\\"assets\\\":[{\\\"id\\\": 1,\\\"required\\\": 1,\\\"title\\\":{\\\"len\\\": 140}},{\\\"id\\\": 4,\\\"required\\\": 1,\\\"img\\\":{\\\"type\\\": 3,\\\"wmin\\\": 600,\\\"hmin\\\": 315}},{\\\"id\\\": 6,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 2,\\\"len\\\": 205}},{\\\"id\\\": 5,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 1,\\\"len\\\": 140}}]}}\"",
+          "ver": "1.1"
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "publisher-id",
+            "tagid": "tag-id"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "http://domain.com",
+      "page": "http://page-domain.com",
+      "ref": "http://page-domain.com"
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+      "h": 300,
+      "w": 300
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://native.whatever.com/12/publisher-id",
+        "body": {
+          "id": "request-id",
+          "imp": [
+            {
+              "id": "impression-id",
+              "native": {
+                "request": "\"{\\\"ver\\\":\\\"1.1\\\",\\\"plcmttype\\\": 4,\\\"plcmtcnt\\\": 1,\\\"assets\\\":[{\\\"id\\\": 1,\\\"required\\\": 1,\\\"title\\\":{\\\"len\\\": 140}},{\\\"id\\\": 4,\\\"required\\\": 1,\\\"img\\\":{\\\"type\\\": 3,\\\"wmin\\\": 600,\\\"hmin\\\": 315}},{\\\"id\\\": 6,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 2,\\\"len\\\": 205}},{\\\"id\\\": 5,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 1,\\\"len\\\": 140}}]}}\"",
+                "ver": "1.1"
+              },
+              "tagid": "tag-id",
+              "ext": {
+                "bidder": {
+                  "publisherId": "publisher-id",
+                  "tagid": "tag-id"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "publisher-id",
+            "name": "publisher-id",
+            "domain": "http://domain.com",
+            "page": "http://page-domain.com",
+            "ref": "http://page-domain.com",
+            "publisher": {
+              "id": "publisher-id"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+            "h": 300,
+            "w": 300
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "123",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "request-id",
+                  "impid": "impression-id",
+                  "price": 2.1,
+                  "adid": "1",
+                  "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":7,\"required\":1,\"img\":{\"url\":\"http://image.taboola.com\",\"w\":600,\"h\":315}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Title For Example\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://clickUrl.com\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://tracker.com\"},{\"event\":1,\"method\":2,\"url\":\"http://tracker-2.com\"}]}&wp=${AUCTION_PRICE}",
+                  "adomain": [
+                    "adomain.comadomain.com"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "lurl": "https://lurl.domain.com/",
+                  "nurl": "nurl.com&wp=${AUCTION_PRICE}"
+                }
+              ],
+              "seat": "1111"
+            }
+          ],
+          "bidid": "123",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "request-id",
+            "impid": "impression-id",
+            "price": 2.1,
+            "adid": "1",
+            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":7,\"required\":1,\"img\":{\"url\":\"http://image.taboola.com\",\"w\":600,\"h\":315}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Title For Example\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://clickUrl.com\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://tracker.com\"},{\"event\":1,\"method\":2,\"url\":\"http://tracker-2.com\"}]}&wp=2.1",
+
+          "adomain": [
+              "adomain.comadomain.com"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "lurl": "https://lurl.domain.com/",
+            "nurl": "nurl.com&wp=2.1"
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/taboola/taboolatest/exemplary/withPageType.json b/adapters/taboola/taboolatest/exemplary/withPageType.json
index c1e303d5..0f0d6435 100644
--- a/adapters/taboola/taboolatest/exemplary/withPageType.json
+++ b/adapters/taboola/taboolatest/exemplary/withPageType.json
@@ -43,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/withPosition.json b/adapters/taboola/taboolatest/exemplary/withPosition.json
index 4f62dfa4..ea1fdbdd 100644
--- a/adapters/taboola/taboolatest/exemplary/withPosition.json
+++ b/adapters/taboola/taboolatest/exemplary/withPosition.json
@@ -43,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
index 56d790db..c4a4279e 100644
--- a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
+++ b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
@@ -45,7 +45,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/bidderServerError.json b/adapters/taboola/taboolatest/supplemental/bidderServerError.json
index 5c1670fb..949ceb93 100644
--- a/adapters/taboola/taboolatest/supplemental/bidderServerError.json
+++ b/adapters/taboola/taboolatest/supplemental/bidderServerError.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
index 435f5d4d..b7d2f2be 100644
--- a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
+++ b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
index fafdd8a7..6e01c457 100644
--- a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
+++ b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
@@ -40,7 +40,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
index 18500f3e..7373d05c 100644
--- a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
+++ b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
index 720398f9..24998b4b 100644
--- a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
+++ b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
@@ -51,7 +51,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
index 580c7d28..cdf39eee 100644
--- a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
+++ b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
@@ -46,7 +46,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
index 706a0ac2..d8069578 100644
--- a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
+++ b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adservertargeting/respdataprocessor_test.go b/adservertargeting/respdataprocessor_test.go
index 15e015cc..3490233d 100644
--- a/adservertargeting/respdataprocessor_test.go
+++ b/adservertargeting/respdataprocessor_test.go
@@ -6,6 +6,8 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb3"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
+	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -559,3 +561,30 @@ func TestGetRespData(t *testing.T) {
 	}
 
 }
+
+func TestResponseObjectStructure(t *testing.T) {
+	// in case BidResponse format will change in next versions this test will show the error
+	// current implementation is up to date with OpenRTB 2.5 and OpenRTB 2.6 formats
+	fieldsToCheck := map[string]reflect.Kind{
+		"id":         reflect.String,
+		"bidid":      reflect.String,
+		"cur":        reflect.String,
+		"customdata": reflect.String,
+		"nbr":        reflect.Pointer,
+	}
+
+	tt := reflect.TypeOf(openrtb2.BidResponse{})
+	fields := reflect.VisibleFields(tt)
+
+	for fieldName, fieldType := range fieldsToCheck {
+		fieldFound := false
+		for _, field := range fields {
+			if fieldName == strings.ToLower(field.Name) {
+				fieldFound = true
+				assert.Equal(t, fieldType, field.Type.Kind(), "incorrect type for field: %s", fieldName)
+				break
+			}
+		}
+		assert.True(t, fieldFound, "field %s is not found in bidResponse object", fieldName)
+	}
+}
diff --git a/config/accounts_test.go b/config/accounts_test.go
index e5eaf78c..20c60532 100644
--- a/config/accounts_test.go
+++ b/config/accounts_test.go
@@ -869,6 +869,8 @@ func TestAccountPriceFloorsValidate(t *testing.T) {
 			description: "valid configuration",
 			pf: &AccountPriceFloors{
 				EnforceFloorsRate: 100,
+				MaxRule:           200,
+				MaxSchemaDims:     10,
 			},
 		},
 		{
@@ -885,6 +887,20 @@ func TestAccountPriceFloorsValidate(t *testing.T) {
 			},
 			want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")},
 		},
+		{
+			description: "Invalid configuration: MaxRule:-20",
+			pf: &AccountPriceFloors{
+				MaxRule: -20,
+			},
+			want: []error{errors.New("account_defaults.price_floors.max_rules should be between 0 and 2147483647")},
+		},
+		{
+			description: "Invalid configuration: MaxSchemaDims:100",
+			pf: &AccountPriceFloors{
+				MaxSchemaDims: 100,
+			},
+			want: []error{errors.New("account_defaults.price_floors.max_schema_dims should be between 0 and 20")},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
diff --git a/config/config_test.go b/config/config_test.go
index 337db814..d63dab8d 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -183,6 +183,8 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment, true)
 	cmpBools(t, "account_defaults.price_floors.enforce_deal_floors", cfg.AccountDefaults.PriceFloors.EnforceDealFloors, false)
 	cmpBools(t, "account_defaults.price_floors.use_dynamic_data", cfg.AccountDefaults.PriceFloors.UseDynamicData, false)
+	cmpInts(t, "account_defaults.price_floors.max_rules", cfg.AccountDefaults.PriceFloors.MaxRule, 100)
+	cmpInts(t, "account_defaults.price_floors.max_schema_dims", cfg.AccountDefaults.PriceFloors.MaxSchemaDims, 3)
 
 	cmpBools(t, "hooks.enabled", cfg.Hooks.Enabled, false)
 	cmpStrings(t, "validations.banner_creative_max_size", cfg.Validations.BannerCreativeMaxSize, "skip")
@@ -450,6 +452,8 @@ account_defaults:
         adjust_for_bid_adjustment: false
         enforce_deal_floors: true
         use_dynamic_data: true
+        max_rules: 120
+        max_schema_dims: 5
 `)
 
 var oldStoredRequestsConfig = []byte(`
@@ -537,6 +541,8 @@ func TestFullConfig(t *testing.T) {
 	cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment, false)
 	cmpBools(t, "account_defaults.price_floors.enforce_deal_floors", cfg.AccountDefaults.PriceFloors.EnforceDealFloors, true)
 	cmpBools(t, "account_defaults.price_floors.use_dynamic_data", cfg.AccountDefaults.PriceFloors.UseDynamicData, true)
+	cmpInts(t, "account_defaults.price_floors.max_rules", cfg.AccountDefaults.PriceFloors.MaxRule, 120)
+	cmpInts(t, "account_defaults.price_floors.max_schema_dims", cfg.AccountDefaults.PriceFloors.MaxSchemaDims, 5)
 
 	//Assert the NonStandardPublishers was correctly unmarshalled
 	assert.Equal(t, []string{"pub1", "pub2"}, cfg.GDPR.NonStandardPublishers, "gdpr.non_standard_publishers")
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 5b125fc5..a8e95e4d 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/prebid/prebid-server/analytics"
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
@@ -398,8 +399,9 @@ func TestOverrideWithParams(t *testing.T) {
 		bidRequest *openrtb2.BidRequest
 	}
 	type testOutput struct {
-		bidRequest *openrtb2.BidRequest
-		errorMsgs  []string
+		bidRequest        *openrtb2.BidRequest
+		errorMsgs         []string
+		expectFatalErrors bool
 	}
 	testCases := []struct {
 		desc     string
@@ -520,7 +522,8 @@ func TestOverrideWithParams(t *testing.T) {
 					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
 					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
 				},
-				errorMsgs: []string{"invalid character 'm' looking for beginning of value"},
+				errorMsgs:         []string{"invalid character 'm' looking for beginning of value"},
+				expectFatalErrors: true,
 			},
 		},
 		{
@@ -567,7 +570,8 @@ func TestOverrideWithParams(t *testing.T) {
 					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
 					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
 				},
-				errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"},
+				errorMsgs:         []string{"invalid character 'm' looking for beginning of object key string"},
+				expectFatalErrors: true,
 			},
 		},
 	}
@@ -579,6 +583,7 @@ func TestOverrideWithParams(t *testing.T) {
 		assert.Len(t, errs, len(test.expected.errorMsgs), test.desc)
 		if len(test.expected.errorMsgs) > 0 {
 			assert.Equal(t, test.expected.errorMsgs[0], errs[0].Error(), test.desc)
+			assert.Equal(t, test.expected.expectFatalErrors, errortypes.ContainsFatalError(errs), test.desc)
 		}
 	}
 }
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 2517530d..41debbbe 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -1118,7 +1118,7 @@ func TestStoredRequests(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testStoreVideoAttr := []bool{true, true, false, false, false}
@@ -1491,7 +1491,7 @@ func TestValidateRequest(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -2023,7 +2023,7 @@ func TestSetIntegrationType(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -2088,7 +2088,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := &openrtb2.BidRequest{}
@@ -2191,7 +2191,7 @@ func TestOversizedRequest(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2229,7 +2229,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2565,7 +2565,7 @@ func TestValidateImpExt(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	for _, group := range testGroups {
@@ -2617,7 +2617,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2664,7 +2664,7 @@ func TestCCPAInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2715,7 +2715,7 @@ func TestNoSaleInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2769,7 +2769,7 @@ func TestValidateSourceTID(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2813,7 +2813,7 @@ func TestSChainInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -3308,7 +3308,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -3617,7 +3617,7 @@ func TestAuctionWarnings(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
@@ -3662,12 +3662,14 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
 
-	resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+	resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 	assert.Nil(t, resReq, "Result request should be nil due to incorrect imp")
 	assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp")
@@ -4237,12 +4239,14 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			assert.NoError(t, resReq.RebuildRequest())
 
@@ -4340,12 +4344,14 @@ func TestParseRequestStoredResponses(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredResponses},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			_, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			_, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			if test.expectedErrorCount == 0 {
 				assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match")
@@ -4428,11 +4434,13 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredBidResponses},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
-			_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			if test.expectedErrorCount == 0 {
 				assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match")
@@ -4462,7 +4470,7 @@ func TestValidateStoredResp(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		&mockStoredResponseFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -5269,12 +5277,14 @@ func TestParseRequestMultiBid(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			assert.NoError(t, resReq.RebuildRequest())
 
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 2eb3fcd3..703cc909 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -16,7 +16,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
-	"github.com/prebid/prebid-server/hooks/hookexecution"
+	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -1224,7 +1224,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 	return deps, metrics, mockModule
 }
@@ -1269,7 +1269,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 }
 
@@ -1292,7 +1292,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames)
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 
 	return deps
@@ -1317,7 +1317,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 
 	return edep
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 5dde9f9b..1eec09d4 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -831,6 +831,179 @@ func TestAdapterCurrency(t *testing.T) {
 	}
 }
 
+func TestFloorsSignalling(t *testing.T) {
+
+	fakeCurrencyClient := &fakeCurrencyRatesHttpClient{
+		responseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`,
+	}
+	currencyConverter := currency.NewRateConverter(
+		fakeCurrencyClient,
+		"currency.com",
+		24*time.Hour,
+	)
+	currencyConverter.Run()
+
+	// Initialize Real Exchange
+	e := exchange{
+		cache: &wellBehavedCache{},
+		me:    &metricsConf.NilMetricsEngine{},
+		gdprPermsBuilder: fakePermissionsBuilder{
+			permissions: &permissionsMock{
+				allowAllBidders: true,
+			},
+		}.Builder,
+		tcf2ConfigBuilder: fakeTCF2ConfigBuilder{
+			cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
+		}.Builder,
+		currencyConverter: currencyConverter,
+		categoriesFetcher: nilCategoryFetcher{},
+		bidIDGenerator:    &mockBidIDGenerator{false, false},
+		floor:             config.PriceFloors{Enabled: true},
+	}
+	e.requestSplitter = requestSplitter{
+		me:                e.me,
+		gdprPermsBuilder:  e.gdprPermsBuilder,
+		tcf2ConfigBuilder: e.tcf2ConfigBuilder,
+	}
+
+	type testResults struct {
+		bidFloor    float64
+		bidFloorCur string
+		err         error
+		resolvedReq string
+	}
+
+	testCases := []struct {
+		desc         string
+		req          *openrtb_ext.RequestWrapper
+		floorsEnable bool
+		expected     testResults
+	}{
+		{
+			desc:         "no update in imp.bidfloor, floors disabled in account config",
+			floorsEnable: false,
+			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
+				ID: "some-request-id",
+				Imp: []openrtb2.Imp{{
+					ID:          "some-impression-id",
+					BidFloor:    15,
+					BidFloorCur: "USD",
+					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+					Ext:         json.RawMessage(`{"prebid":{}}`),
+				}},
+				Site: &openrtb2.Site{
+					Page:   "prebid.org",
+					Ext:    json.RawMessage(`{"amp":0}`),
+					Domain: "www.website.com",
+				},
+				Cur: []string{"USD"},
+				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":11,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
+			}},
+			expected: testResults{
+				bidFloor:    15.00,
+				bidFloorCur: "USD",
+			},
+		},
+		{
+			desc:         "no update in imp.bidfloor due to no rule matched",
+			floorsEnable: true,
+			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
+				ID: "some-request-id",
+				Imp: []openrtb2.Imp{{
+					ID:          "some-impression-id",
+					BidFloor:    15,
+					BidFloorCur: "USD",
+					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+					Ext:         json.RawMessage(`{"prebid":{}}`),
+				}},
+				Site: &openrtb2.Site{
+					Page:   "prebid.org",
+					Ext:    json.RawMessage(`{"amp":0}`),
+					Domain: "www.website.com",
+				},
+				Cur: []string{"USD"},
+				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website123.com":10,"*|*|www.test.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
+			}},
+			expected: testResults{
+				bidFloor:    15.00,
+				bidFloorCur: "USD",
+			},
+		},
+		{
+			desc:         "update imp.bidfloor with matched rule value",
+			floorsEnable: true,
+			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
+				ID: "some-request-id",
+				Imp: []openrtb2.Imp{{
+					ID:          "some-impression-id",
+					BidFloor:    15,
+					BidFloorCur: "USD",
+					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+					Ext:         json.RawMessage(`{"prebid":{}}`),
+				}},
+				Site: &openrtb2.Site{
+					Page:   "prebid.org",
+					Ext:    json.RawMessage(`{"amp":0}`),
+					Domain: "www.website.com",
+				},
+				Cur: []string{"USD"},
+				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":10,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
+			}},
+			expected: testResults{
+				bidFloor:    10.00,
+				bidFloorCur: "USD",
+			},
+		},
+		{
+			desc:         "update resolved request with floors details",
+			floorsEnable: true,
+			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
+				ID: "some-request-id",
+				Imp: []openrtb2.Imp{{
+					ID:          "some-impression-id",
+					BidFloor:    15,
+					BidFloorCur: "USD",
+					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+					Ext:         json.RawMessage(`{"prebid":{}}`),
+				}},
+				Site: &openrtb2.Site{
+					Page:   "prebid.org",
+					Ext:    json.RawMessage(`{"amp":0}`),
+					Domain: "www.website.com",
+				},
+				Test: 1,
+				Cur:  []string{"USD"},
+				Ext:  json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":11,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
+			}},
+			expected: testResults{
+				bidFloor:    11.00,
+				bidFloorCur: "USD",
+				resolvedReq: `{"id":"some-request-id","imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"bidfloor":11,"bidfloorcur":"USD","ext":{"prebid":{"floors":{"floorrule":"banner|300x250|www.website.com","floorrulevalue":11,"floorvalue":11}}}}],"site":{"domain":"www.website.com","page":"prebid.org","ext":{"amp":0}},"test":1,"cur":["USD"],"ext":{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":20,"*|*|www.test.com":15,"banner|300x250|www.website.com":11},"default":50}]},"enabled":true,"skipped":false,"fetchstatus":"none","location":"request"}}}}`,
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		auctionRequest := AuctionRequest{
+			BidRequestWrapper: test.req,
+			Account:           config.Account{DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: test.floorsEnable, MaxRule: 100, MaxSchemaDims: 5}},
+			UserSyncs:         &emptyUsersync{},
+			HookExecutor:      &hookexecution.EmptyHookExecutor{},
+		}
+		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
+
+		// Assertions
+		assert.Equal(t, test.expected.err, err, "Error")
+		assert.Equal(t, test.expected.bidFloor, auctionRequest.BidRequestWrapper.Imp[0].BidFloor, "Floor Value")
+		assert.Equal(t, test.expected.bidFloorCur, auctionRequest.BidRequestWrapper.Imp[0].BidFloorCur, "Floor Currency")
+
+		if test.req.Test == 1 {
+			actualResolvedRequest, _, _, _ := jsonparser.Get(outBidResponse.Ext, "debug", "resolvedrequest")
+			assert.JSONEq(t, test.expected.resolvedReq, string(actualResolvedRequest), "Resolved request is incorrect")
+		}
+	}
+
+}
 func TestGetAuctionCurrencyRates(t *testing.T) {
 
 	pbsRates := map[string]map[string]float64{
diff --git a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json
new file mode 100644
index 00000000..627c95f9
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json
@@ -0,0 +1,133 @@
+{
+    "requestType": "amp",
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [
+                {
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                }
+                            }
+                        },
+                        "data": {
+                            "keywords": "prebid server example"
+                        },
+                        "context": {
+                            "data": {
+                                "keywords": "another prebid server example"
+                            }
+                        }
+                    }
+                }
+            ]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                },
+                                "data": {
+                                    "keywords": "prebid server example"
+                                },
+                                "context": {
+                                    "data": {
+                                        "keywords": "another prebid server example"
+                                    }
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "some-imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 500,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "banner"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "banner"
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/floors/floors_test.go b/floors/floors_test.go
new file mode 100644
index 00000000..7e33e7c8
--- /dev/null
+++ b/floors/floors_test.go
@@ -0,0 +1,673 @@
+package floors
+
+import (
+	"encoding/json"
+	"errors"
+	"testing"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsRequestEnabledWithFloor(t *testing.T) {
+	FalseFlag := false
+	TrueFlag := true
+
+	testCases := []struct {
+		name string
+		in   *openrtb_ext.ExtRequest
+		out  bool
+	}{
+		{
+			name: "Request With Nil Floors",
+			in:   &openrtb_ext.ExtRequest{},
+			out:  true,
+		},
+		{
+			name: "Request With Floors Disabled",
+			in:   &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Floors: &openrtb_ext.PriceFloorRules{Enabled: &FalseFlag}}},
+			out:  false,
+		},
+		{
+			name: "Request With Floors Enabled",
+			in:   &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Floors: &openrtb_ext.PriceFloorRules{Enabled: &TrueFlag}}},
+			out:  true,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			out := tc.in.Prebid.Floors.GetEnabled()
+			assert.Equal(t, out, tc.out, tc.name)
+		})
+	}
+}
+
+func getCurrencyRates(rates map[string]map[string]float64) currency.Conversions {
+	return currency.NewRates(rates)
+}
+
+func TestEnrichWithPriceFloors(t *testing.T) {
+	rates := map[string]map[string]float64{
+		"USD": {
+			"INR": 70,
+			"EUR": 0.9,
+			"JPY": 5.09,
+		},
+	}
+
+	testAccountConfig := config.Account{
+		PriceFloors: config.AccountPriceFloors{
+			Enabled:        true,
+			UseDynamicData: false,
+			MaxRule:        100,
+			MaxSchemaDims:  5,
+		},
+	}
+
+	width := int64(300)
+	height := int64(600)
+
+	testCases := []struct {
+		name              string
+		bidRequestWrapper *openrtb_ext.RequestWrapper
+		account           config.Account
+		conversions       currency.Conversions
+		Skipped           bool
+		err               string
+		expFloorVal       float64
+		expFloorCur       string
+		expPriceFlrLoc    string
+		expSchemaVersion  string
+	}{
+		{
+			name: "Floors disabled in account config",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					Enabled: false,
+				},
+			},
+			err: "Floors feature is disabled at account or in the request",
+		},
+		{
+			name: "Floors disabled in req.ext.prebid.floors.Enabled=false config",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":false,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account: testAccountConfig,
+			err:     "Floors feature is disabled at account or in the request",
+		},
+		{
+			name: "Floors enabled in req.ext.prebid.floors.Enabled and enabled account config",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 2 from req","currency":"USD","values":{"banner|300x250|www.website.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}},{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x250|www.website.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    5,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "Skiprate = 100, Floors enabled in  req.ext.prebid.floors.Enabled and account config: Floors singalling skipped ",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x250|www.website.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"skiprate": 100,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account: testAccountConfig,
+			Skipped: true,
+		},
+		{
+			name: "Single ModelGroup, Invalid Skiprate = 110: Floors singalling skipped",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x250|www.website.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"skiprate": 110,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			err:            "Invalid SkipRate = '110' at ext.prebid.floors.skiprate",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "Multiple ModelGroups, Invalid Skiprate = 110: in one group, Floors singalling done using second ModelGroup",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":11,"floormincur":"USD","data":{"currency":"USD","floorsschemaversion":"2","modelgroups":[{"modelweight":50,"modelversion":"version2","schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":11.01,"*|*|www.website1.com":17.01},"default":21},{"modelweight":50,"modelversion":"version11","skiprate":110,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|300x250|*":11.01,"*|300x250|www.website1.com":100.01},"default":21}]},"enforcement":{"enforcepbs":true,"floordeals":true},"enabled":true}}}`),
+				},
+			},
+			account:          testAccountConfig,
+			err:              "Invalid Floor Model = 'version11' due to SkipRate = '110' is out of range (1-100)",
+			expFloorVal:      11.01,
+			expFloorCur:      "USD",
+			expPriceFlrLoc:   openrtb_ext.RequestLocation,
+			expSchemaVersion: "2",
+		},
+		{
+			name: "Rule selection with Site object, banner|300x600|www.website.com",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{W: &width, H: &height}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"BANNER|300x600|WWW.WEBSITE.COM":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    5,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "Rule selection with App object, *|*|www.test.com",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					App: &openrtb2.App{
+						Domain: "www.test.com",
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{W: &width, H: &height}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website.com":5,"*|*|www.test.com":15,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    15,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "Floors Signalling not done as req.ext.prebid.floors not provided",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					App: &openrtb2.App{
+						Domain: "www.test.com",
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", BidFloor: 10, BidFloorCur: "EUR", Banner: &openrtb2.Banner{W: &width, H: &height}}},
+					Ext: json.RawMessage(`{"prebid":{}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    10,
+			expFloorCur:    "EUR",
+			expPriceFlrLoc: openrtb_ext.NoDataLocation,
+			err:            "Empty Floors data",
+		},
+		{
+			name: "BidFloor(USD) Less than MinBidFloor(INR) with different currency",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":80,"floormincur":"INR","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x250|www.website.com":1,"*|*|www.test.com":15,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    1.1429,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "BidFloor(INR) Less than MinBidFloor(USD) with different currency",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"INR","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":60,"*|*|www.test.com":65,"*|*|*":67},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    70,
+			expFloorCur:    "INR",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "BidFloor is greater than MinBidFloor with same currency",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":2,"*|*|www.test.com":1.5,"*|*|*":1.7},"Default":5,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    2,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "BidFloor Less than MinBidFloor with same currency",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":3,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":2,"*|*|www.test.com":1.5,"*|*|*":1.7},"Default":5,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    3,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "No Rule matching, default value provided",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com1":2,"*|*|www.test2.com":1.5},"schema":{"fields":["mediaType","size","domain"]}, "default": 20}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    20,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "No Rule matching, default value less than floorMin",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":15,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com1":2,"*|*|www.test2.com":1.5},"schema":{"fields":["mediaType","size","domain"]}, "default": 5}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    15,
+			expFloorCur:    "USD",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name: "imp.bidfloor provided, No Rule matching, MinBidFloor provided",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", BidFloor: 100, BidFloorCur: "INR", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":2,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com1":2,"*|*|www.test2.com":1.5},"schema":{"fields":["mediaType","size","domain"]}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			expFloorVal:    100,
+			expFloorCur:    "INR",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+		{
+			name:              "Empty RequestWrapper ",
+			bidRequestWrapper: nil,
+			account:           testAccountConfig,
+			err:               "Empty bidrequest",
+		},
+		{
+			name: "Invalid Floor Min Currency",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":80,"floormincur":"ABCD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x250|www.website.com":1,"*|*|www.test.com":15,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account:        testAccountConfig,
+			err:            "Error in getting FloorMin value : 'currency: tag is not well-formed'",
+			expPriceFlrLoc: openrtb_ext.RequestLocation,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+
+			ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates))
+			if tc.bidRequestWrapper != nil {
+				assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloor, tc.expFloorVal, tc.name)
+				assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloorCur, tc.expFloorCur, tc.name)
+				requestExt, err := tc.bidRequestWrapper.GetRequestExt()
+				if err == nil {
+					if tc.Skipped {
+						assert.Equal(t, *requestExt.GetPrebid().Floors.Skipped, tc.Skipped, tc.name)
+					} else {
+						assert.Equal(t, requestExt.GetPrebid().Floors.PriceFloorLocation, tc.expPriceFlrLoc, tc.name)
+						if tc.expSchemaVersion != "" {
+							assert.Equal(t, requestExt.GetPrebid().Floors.Data.FloorsSchemaVersion, tc.expSchemaVersion, tc.name)
+						}
+					}
+				}
+			}
+			if len(ErrList) > 0 {
+				assert.Equal(t, ErrList[0].Error(), tc.err, tc.name)
+			}
+		})
+	}
+}
+
+func getTrue() *bool {
+	trueFlag := true
+	return &trueFlag
+}
+
+func TestResolveFloors(t *testing.T) {
+	rates := map[string]map[string]float64{
+		"USD": {
+			"INR": 70,
+			"EUR": 0.9,
+			"JPY": 5.09,
+		},
+	}
+
+	testCases := []struct {
+		name              string
+		bidRequestWrapper *openrtb_ext.RequestWrapper
+		account           config.Account
+		conversions       currency.Conversions
+		expErr            []error
+		expFloors         *openrtb_ext.PriceFloorRules
+	}{
+		{
+			name: "Dynamic fetch disabled, only enforcement object present in req.ext",
+			bidRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Site: &openrtb2.Site{
+						Publisher: &openrtb2.Publisher{Domain: "www.website.com"},
+					},
+					Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+					Ext: json.RawMessage(`{"prebid":{"floors":{"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`),
+				},
+			},
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					Enabled:        true,
+					UseDynamicData: false,
+				},
+			},
+			expFloors: &openrtb_ext.PriceFloorRules{
+				Enforcement: &openrtb_ext.PriceFloorEnforcement{
+					EnforcePBS:  getTrue(),
+					EnforceRate: 100,
+					FloorDeals:  getTrue(),
+				},
+				FetchStatus:        openrtb_ext.FetchNone,
+				PriceFloorLocation: openrtb_ext.RequestLocation,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates))
+			assert.Equal(t, resolvedFloors, tc.expFloors, tc.name)
+		})
+	}
+}
+
+func printFloors(floors *openrtb_ext.PriceFloorRules) string {
+	fbytes, _ := json.Marshal(floors)
+	return string(fbytes)
+}
+
+func TestCreateFloorsFrom(t *testing.T) {
+
+	testAccountConfig := config.Account{
+		PriceFloors: config.AccountPriceFloors{
+			Enabled:        true,
+			UseDynamicData: false,
+			MaxRule:        100,
+			MaxSchemaDims:  5,
+		},
+	}
+
+	type args struct {
+		floors        *openrtb_ext.PriceFloorRules
+		account       config.Account
+		fetchStatus   string
+		floorLocation string
+	}
+	testCases := []struct {
+		name  string
+		args  args
+		want  *openrtb_ext.PriceFloorRules
+		want1 []error
+	}{
+		{
+			name: "floor provider should be selected from floor json",
+			args: args{
+				account: testAccountConfig,
+				floors: &openrtb_ext.PriceFloorRules{
+					Enabled:     getTrue(),
+					FloorMin:    10.11,
+					FloorMinCur: "EUR",
+					Enforcement: &openrtb_ext.PriceFloorEnforcement{
+						EnforcePBS:  getTrue(),
+						EnforceRate: 100,
+						FloorDeals:  getTrue(),
+					},
+					Data: &openrtb_ext.PriceFloorData{
+						Currency: "USD",
+						ModelGroups: []openrtb_ext.PriceFloorModelGroup{
+							{
+								ModelVersion: "model from fetched",
+								Currency:     "USD",
+								Values: map[string]float64{
+									"banner|300x600|www.website5.com": 15,
+									"*|*|*":                           25,
+								},
+								Schema: openrtb_ext.PriceFloorSchema{
+									Fields: []string{"mediaType", "size", "domain"},
+								},
+							},
+						},
+						FloorProvider: "PM",
+					},
+				},
+				fetchStatus:   openrtb_ext.FetchSuccess,
+				floorLocation: openrtb_ext.FetchLocation,
+			},
+			want: &openrtb_ext.PriceFloorRules{
+				Enabled:            getTrue(),
+				FloorMin:           10.11,
+				FloorMinCur:        "EUR",
+				FetchStatus:        openrtb_ext.FetchSuccess,
+				PriceFloorLocation: openrtb_ext.FetchLocation,
+				Enforcement: &openrtb_ext.PriceFloorEnforcement{
+					EnforcePBS:  getTrue(),
+					EnforceRate: 100,
+					FloorDeals:  getTrue(),
+				},
+				Data: &openrtb_ext.PriceFloorData{
+					Currency: "USD",
+					ModelGroups: []openrtb_ext.PriceFloorModelGroup{
+						{
+							ModelVersion: "model from fetched",
+							Currency:     "USD",
+							Values: map[string]float64{
+								"banner|300x600|www.website5.com": 15,
+								"*|*|*":                           25,
+							},
+							Schema: openrtb_ext.PriceFloorSchema{
+								Fields: []string{"mediaType", "size", "domain"},
+							},
+						},
+					},
+					FloorProvider: "PM",
+				},
+			},
+		},
+		{
+			name: "floor provider will be empty if no value provided in floor json",
+			args: args{
+				account: testAccountConfig,
+				floors: &openrtb_ext.PriceFloorRules{
+					Enabled:     getTrue(),
+					FloorMin:    10.11,
+					FloorMinCur: "EUR",
+					Enforcement: &openrtb_ext.PriceFloorEnforcement{
+						EnforcePBS:  getTrue(),
+						EnforceRate: 100,
+						FloorDeals:  getTrue(),
+					},
+					Data: &openrtb_ext.PriceFloorData{
+						Currency: "USD",
+						ModelGroups: []openrtb_ext.PriceFloorModelGroup{
+							{
+								ModelVersion: "model from request",
+								Currency:     "USD",
+								Values: map[string]float64{
+									"banner|300x600|www.website5.com": 15,
+									"*|*|*":                           25,
+								},
+								Schema: openrtb_ext.PriceFloorSchema{
+									Fields: []string{"mediaType", "size", "domain"},
+								},
+							},
+						},
+						FloorProvider: "",
+					},
+				},
+				fetchStatus:   openrtb_ext.FetchSuccess,
+				floorLocation: openrtb_ext.FetchLocation,
+			},
+			want: &openrtb_ext.PriceFloorRules{
+				Enabled:            getTrue(),
+				FloorMin:           10.11,
+				FloorMinCur:        "EUR",
+				FetchStatus:        openrtb_ext.FetchSuccess,
+				PriceFloorLocation: openrtb_ext.FetchLocation,
+				Enforcement: &openrtb_ext.PriceFloorEnforcement{
+					EnforcePBS:  getTrue(),
+					EnforceRate: 100,
+					FloorDeals:  getTrue(),
+				},
+				Data: &openrtb_ext.PriceFloorData{
+					Currency: "USD",
+					ModelGroups: []openrtb_ext.PriceFloorModelGroup{
+						{
+							ModelVersion: "model from request",
+							Currency:     "USD",
+							Values: map[string]float64{
+								"banner|300x600|www.website5.com": 15,
+								"*|*|*":                           25,
+							},
+							Schema: openrtb_ext.PriceFloorSchema{
+								Fields: []string{"mediaType", "size", "domain"},
+							},
+						},
+					},
+					FloorProvider: "",
+				},
+			},
+		},
+		{
+			name: "only floor enforcement object present",
+			args: args{
+				account: testAccountConfig,
+				floors: &openrtb_ext.PriceFloorRules{
+					Enabled: getTrue(),
+					Enforcement: &openrtb_ext.PriceFloorEnforcement{
+						EnforcePBS:  getTrue(),
+						EnforceRate: 100,
+						FloorDeals:  getTrue(),
+					},
+				},
+				fetchStatus:   openrtb_ext.FetchNone,
+				floorLocation: openrtb_ext.RequestLocation,
+			},
+			want: &openrtb_ext.PriceFloorRules{
+				FetchStatus:        openrtb_ext.FetchNone,
+				PriceFloorLocation: openrtb_ext.RequestLocation,
+				Enforcement: &openrtb_ext.PriceFloorEnforcement{
+					EnforcePBS:  getTrue(),
+					EnforceRate: 100,
+					FloorDeals:  getTrue(),
+				},
+			},
+		},
+		{
+			name: "Invalid modelGroup with skipRate = 110",
+			args: args{
+				account: testAccountConfig,
+				floors: &openrtb_ext.PriceFloorRules{
+					Enabled: getTrue(),
+					Data: &openrtb_ext.PriceFloorData{
+						Currency: "USD",
+						ModelGroups: []openrtb_ext.PriceFloorModelGroup{
+							{
+								ModelVersion: "model from fetched",
+								Currency:     "USD",
+								SkipRate:     110,
+								Values: map[string]float64{
+									"banner|300x600|www.website5.com": 15,
+									"*|*|*":                           25,
+								},
+								Schema: openrtb_ext.PriceFloorSchema{
+									Fields: []string{"mediaType", "size", "domain"},
+								},
+							},
+						},
+					},
+				},
+				fetchStatus:   openrtb_ext.FetchNone,
+				floorLocation: openrtb_ext.RequestLocation,
+			},
+			want: &openrtb_ext.PriceFloorRules{
+				FetchStatus:        openrtb_ext.FetchNone,
+				PriceFloorLocation: openrtb_ext.RequestLocation,
+			},
+			want1: []error{
+				errors.New("Invalid Floor Model = 'model from fetched' due to SkipRate = '110' is out of range (1-100)"),
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, got1 := createFloorsFrom(tc.args.floors, tc.args.account, tc.args.fetchStatus, tc.args.floorLocation)
+			assert.Equal(t, got1, tc.want1, tc.name)
+			assert.Equal(t, got, tc.want, tc.name)
+		})
+	}
+}
diff --git a/floors/rule_test.go b/floors/rule_test.go
new file mode 100644
index 00000000..e309344d
--- /dev/null
+++ b/floors/rule_test.go
@@ -0,0 +1,1214 @@
+package floors
+
+import (
+	"encoding/json"
+	"errors"
+	"testing"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPrepareRuleCombinations(t *testing.T) {
+	testCases := []struct {
+		name string
+		in   []string
+		del  string
+		exp  []string
+	}{
+		{
+			name: "Schema items, n = 1",
+			in:   []string{"A"},
+			del:  "|",
+			exp: []string{
+				"a",
+				"*",
+			},
+		},
+		{
+			name: "Schema items, n = 2",
+			in:   []string{"A", "B"},
+			del:  "|",
+			exp: []string{
+				"a|b",
+				"a|*",
+				"*|b",
+				"*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3",
+			in:   []string{"A", "B", "C"},
+			del:  "|",
+			exp: []string{
+				"a|b|c",
+				"a|b|*",
+				"a|*|c",
+				"*|b|c",
+				"a|*|*",
+				"*|b|*",
+				"*|*|c",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 4",
+			in:   []string{"A", "B", "C", "D"},
+			del:  "|",
+			exp: []string{
+				"a|b|c|d",
+				"a|b|c|*",
+				"a|b|*|d",
+				"a|*|c|d",
+				"*|b|c|d",
+				"a|b|*|*",
+				"a|*|c|*",
+				"a|*|*|d",
+				"*|b|c|*",
+				"*|b|*|d",
+				"*|*|c|d",
+				"a|*|*|*",
+				"*|b|*|*",
+				"*|*|c|*",
+				"*|*|*|d",
+				"*|*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 1 with wildcards",
+			in:   []string{"*"},
+			del:  "|",
+			exp: []string{
+				"*",
+			},
+		},
+		{
+			name: "Schema items, n = 2 with wildcard at index = 0",
+			in:   []string{"*", "B"},
+			del:  "|",
+			exp: []string{
+				"*|b",
+				"*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 2 with wildcards at index = 1",
+			in:   []string{"A", "*"},
+			del:  "|",
+			exp: []string{
+				"a|*",
+				"*|*",
+			},
+		},
+
+		{
+			name: "Schema items, n = 2 wildcards at index = 0,1",
+			in:   []string{"*", "*"},
+			del:  "|",
+			exp: []string{
+				"*|*",
+			},
+		},
+
+		{
+			name: "Schema items, n = 3 wildcard at index = 0",
+			in:   []string{"*", "B", "C"},
+			del:  "|",
+			exp: []string{
+				"*|b|c",
+				"*|b|*",
+				"*|*|c",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3 wildcard at index = 1",
+			in:   []string{"A", "*", "C"},
+			del:  "|",
+			exp: []string{
+				"a|*|c",
+				"a|*|*",
+				"*|*|c",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3 with wildcard at index = 2",
+			in:   []string{"A", "B", "*"},
+			del:  "|",
+			exp: []string{
+				"a|b|*",
+				"a|*|*",
+				"*|b|*",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3 with wildcard at index = 0,2",
+			in:   []string{"*", "B", "*"},
+			del:  "|",
+			exp: []string{
+				"*|b|*",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3 with wildcard at index = 0,1",
+			in:   []string{"*", "*", "C"},
+			del:  "|",
+			exp: []string{
+				"*|*|c",
+				"*|*|*",
+			},
+		},
+		{
+			name: "Schema items, n = 3 with wildcard at index = 1,2",
+			in:   []string{"A", "*", "*"},
+			del:  "|",
+			exp: []string{
+				"a|*|*",
+				"*|*|*",
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			act := prepareRuleCombinations(tc.in, tc.del)
+			assert.Equal(t, tc.exp, act, tc.name)
+		})
+	}
+}
+
+func TestUpdateImpExtWithFloorDetails(t *testing.T) {
+	testCases := []struct {
+		name         string
+		matchedRule  string
+		floorRuleVal float64
+		floorVal     float64
+		imp          *openrtb_ext.ImpWrapper
+		expected     json.RawMessage
+	}{
+		{
+			name:         "Nil ImpExt",
+			matchedRule:  "test|123|xyz",
+			floorRuleVal: 5.5,
+			floorVal:     5.5,
+			imp:          &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}}},
+			expected:     []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`),
+		},
+		{
+			name:         "Empty ImpExt",
+			matchedRule:  "test|123|xyz",
+			floorRuleVal: 5.5,
+			floorVal:     5.5,
+			imp:          &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: json.RawMessage{}}},
+			expected:     []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`),
+		},
+		{
+			name:         "With prebid Ext",
+			matchedRule:  "banner|www.test.com|*",
+			floorRuleVal: 5.5,
+			floorVal:     15.5,
+			imp:          &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: []byte(`{"prebid": {"test": true}}`)}},
+			expected:     []byte(`{"prebid":{"floors":{"floorrule":"banner|www.test.com|*","floorrulevalue":5.5,"floorvalue":15.5}}}`),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			updateImpExtWithFloorDetails(tc.imp, tc.matchedRule, tc.floorRuleVal, tc.floorVal)
+			_ = tc.imp.RebuildImp()
+			if tc.imp.Ext != nil {
+				assert.Equal(t, tc.imp.Ext, tc.expected, tc.name)
+			}
+		})
+	}
+}
+
+func TestCreateRuleKeys(t *testing.T) {
+	testCases := []struct {
+		name        string
+		floorSchema openrtb_ext.PriceFloorSchema
+		request     *openrtb2.BidRequest
+		out         []string
+	}{
+		{
+			name: "CreateRule with banner mediatype, size and domain",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Domain: "www.test.com",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}},
+				Ext: json.RawMessage(`{"prebid": { "floors": {"data": {"currency": "USD","skipRate": 0,"schema": {"fields": [ "mediaType", "size", "domain" ] },"values": {  "banner|300x250|www.website.com": 1.01, "banner|300x250|*": 2.01, "banner|300x600|www.website.com": 3.01,  "banner|300x600|*": 4.01, "banner|728x90|www.website.com": 5.01, "banner|728x90|*": 6.01, "banner|*|www.website.com": 7.01, "banner|*|*": 8.01, "*|300x250|www.website.com": 9.01, "*|300x250|*": 10.01, "*|300x600|www.website.com": 11.01,  "*|300x600|*": 12.01,  "*|728x90|www.website.com": 13.01, "*|728x90|*": 14.01,  "*|*|www.website.com": 15.01, "*|*|*": 16.01  }, "default": 1}}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "size", "domain"}},
+			out:         []string{"banner", "300x250", "www.test.com"},
+		},
+		{
+			name: "CreateRule with video mediatype, size and domain",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Domain: "www.test.com",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: 640, H: 480, Placement: 1}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "size", "domain"}},
+			out:         []string{"video", "640x480", "www.test.com"},
+		},
+		{
+			name: "CreateRule with video mediatype, size and domain",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Domain: "www.test.com",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250, Placement: 2}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "size", "domain"}},
+			out:         []string{"video-outstream", "300x250", "www.test.com"},
+		},
+		{
+			name: "CreateRule with audio mediatype, adUnitCode and domain",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Domain: "www.test.com",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", TagID: "tag123", Audio: &openrtb2.Audio{MaxDuration: 300}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "adUnitCode", "siteDomain"}},
+			out:         []string{"audio", "tag123", "www.test.com"},
+		},
+		{
+			name: "CreateRule with audio mediatype, adUnitCode=* and domain",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Domain: "www.test.com",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Audio: &openrtb2.Audio{MaxDuration: 300}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "adUnitCode", "siteDomain"}},
+			out:         []string{"audio", "*", "www.test.com"},
+		},
+		{
+			name: "CreateRule with native mediatype, bundle and domain",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Domain: "www.test.com",
+					Bundle: "bundle123",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "bundle", "siteDomain"}},
+			out:         []string{"native", "bundle123", "www.test.com"},
+		},
+		{
+			name: "CreateRule with native, banner mediatype, bundle and domain",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Domain: "www.test.com",
+					Bundle: "bundle123",
+				},
+				Imp: []openrtb2.Imp{{ID: "1234", Audio: &openrtb2.Audio{MaxDuration: 300}, Native: &openrtb2.Native{Request: "Test"}}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "bundle", "siteDomain"}},
+			out:         []string{"*", "bundle123", "www.test.com"},
+		},
+		{
+			name: "CreateRule with channel, country, deviceType",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "tablet"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+				Ext:    json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"channel", "country", "deviceType"}},
+			out:         []string{"chName", "USA", "tablet"},
+		},
+		{
+			name: "CreateRule with channel, country, deviceType=tablet",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "Windows NT touch"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+				Ext:    json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"channel", "country", "deviceType"}},
+			out:         []string{"chName", "USA", "tablet"},
+		},
+		{
+			name: "CreateRule with channel, country, deviceType=desktop",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "Windows NT"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+				Ext:    json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"channel", "country", "deviceType"}},
+			out:         []string{"chName", "USA", "desktop"},
+		},
+		{
+			name: "CreateRule with channel, country, deviceType=desktop",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "Windows NT"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+				Ext:    json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"channel", "country", "deviceType"}},
+			out:         []string{"chName", "USA", "desktop"},
+		},
+		{
+			name: "CreateRule with channel, size, deviceType=desktop",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "SomeDevice"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 100, H: 200}, {W: 200, H: 300}}}}},
+				Ext:    json.RawMessage(`{"prebid": {"test": "1}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"channel", "size", "deviceType"}},
+			out:         []string{"*", "*", "desktop"},
+		},
+		{
+			name: "CreateRule with pubDomain, country, deviceType",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+					Bundle: "bundle123",
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}, UA: "Phone"},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"}}},
+				Ext:    json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"pubDomain", "country", "deviceType"}},
+			out:         []string{"www.test.com", "USA", "phone"},
+		},
+		{
+			name: "CreateRule with pubDomain, gptSlot, deviceType",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp: []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"},
+					Ext: json.RawMessage(`{"data": {"adserver": {"name": "gam","adslot": "adslot123"}, "pbadslot": "pbadslot123"}}`),
+				}},
+				Ext: json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"pubDomain", "gptSlot", "deviceType"}},
+			out:         []string{"www.test.com", "adslot123", "*"},
+		},
+		{
+			name: "CreateRule with pubDomain, gptSlot, deviceType",
+			request: &openrtb2.BidRequest{
+				Site: &openrtb2.Site{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp: []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"},
+					Ext: json.RawMessage(`{"data": {"adserver": {"name": "test","adslot": "adslot123"}, "pbadslot": "pbadslot123"}}`),
+				}},
+				Ext: json.RawMessage(`{"prebid": {"channel": {"name": "chName","version": "ver1"}}}`),
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"pubDomain", "gptSlot", "deviceType"}},
+			out:         []string{"www.test.com", "pbadslot123", "*"},
+		},
+		{
+			name: "CreateRule with domain, adUnitCode, channel",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp: []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"},
+					Ext: json.RawMessage(`{"data": {"adserver": {"name": "test","adslot": "adslot123"}, "pbadslot": "pbadslot123"}}`),
+				}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"domain", "adUnitCode", "channel"}},
+			out:         []string{"www.test.com", "pbadslot123", "*"},
+		},
+		{
+			name: "CreateRule with domain, adUnitCode, channel",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp: []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{Request: "Test"},
+					Ext: json.RawMessage(`{"gpid":  "gpid_134"}`),
+				}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"domain", "adUnitCode", "channel"}},
+			out:         []string{"www.test.com", "gpid_134", "*"},
+		},
+		{
+			name: "CreateRule with domain, adUnitCode, channel",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{}, Ext: json.RawMessage(`{"prebid": {"storedrequest": {"id": "storedid_123"}}}`)}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"domain", "adUnitCode", "channel"}},
+			out:         []string{"www.test.com", "storedid_123", "*"},
+		},
+		{
+			name: "CreateRule with domain, adUnitCode, channel",
+			request: &openrtb2.BidRequest{
+				App: &openrtb2.App{
+					Publisher: &openrtb2.Publisher{
+						Domain: "www.test.com",
+					},
+				},
+				Device: &openrtb2.Device{Geo: &openrtb2.Geo{Country: "USA"}},
+				Imp:    []openrtb2.Imp{{ID: "1234", Native: &openrtb2.Native{}, Ext: json.RawMessage(`{"prebid": {"storedrequest": {"id": "storedid_123"}}}`)}},
+			},
+			floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"domain", "adUnitCode", "channel"}},
+			out:         []string{"www.test.com", "storedid_123", "*"},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			out := createRuleKey(tc.floorSchema, &openrtb_ext.RequestWrapper{BidRequest: tc.request}, &openrtb_ext.ImpWrapper{Imp: &tc.request.Imp[0]})
+			assert.Equal(t, out, tc.out, tc.name)
+		})
+	}
+}
+
+func TestShouldSkipFloors(t *testing.T) {
+
+	testCases := []struct {
+		name                string
+		ModelGroupsSkipRate int
+		DataSkipRate        int
+		RootSkipRate        int
+		out                 bool
+		randomGen           func(int) int
+	}{
+		{
+			name:                "ModelGroupsSkipRate=10 with skip = true",
+			ModelGroupsSkipRate: 10,
+			DataSkipRate:        0,
+			RootSkipRate:        0,
+			randomGen:           func(i int) int { return 5 },
+			out:                 true,
+		},
+		{
+			name:                "ModelGroupsSkipRate=100 with skip = true",
+			ModelGroupsSkipRate: 100,
+			DataSkipRate:        0,
+			RootSkipRate:        0,
+			randomGen:           func(i int) int { return 5 },
+			out:                 true,
+		},
+		{
+			name:                "ModelGroupsSkipRate=0 with skip = false",
+			ModelGroupsSkipRate: 0,
+			DataSkipRate:        0,
+			RootSkipRate:        0,
+			randomGen:           func(i int) int { return 0 },
+			out:                 false,
+		},
+		{
+			name:                "DataSkipRate=50  with with skip = true",
+			ModelGroupsSkipRate: 0,
+			DataSkipRate:        50,
+			RootSkipRate:        0,
+			randomGen:           func(i int) int { return 40 },
+			out:                 true,
+		},
+		{
+			name:                "RootSkipRate=50  with with skip = true",
+			ModelGroupsSkipRate: 0,
+			DataSkipRate:        0,
+			RootSkipRate:        60,
+			randomGen:           func(i int) int { return 40 },
+			out:                 true,
+		},
+		{
+			name:                "RootSkipRate=50  with with skip = false",
+			ModelGroupsSkipRate: 0,
+			DataSkipRate:        0,
+			RootSkipRate:        60,
+			randomGen:           func(i int) int { return 70 },
+			out:                 false,
+		},
+		{
+			name:                "RootSkipRate=100  with with skip = true",
+			ModelGroupsSkipRate: 0,
+			DataSkipRate:        0,
+			RootSkipRate:        100,
+			randomGen:           func(i int) int { return 100 },
+			out:                 true,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			out := shouldSkipFloors(tc.ModelGroupsSkipRate, tc.DataSkipRate, tc.RootSkipRate, tc.randomGen)
+			assert.Equal(t, out, tc.out, tc.name)
+		})
+	}
+
+}
+
+func TestSelectFloorModelGroup(t *testing.T) {
+	weightNilModelGroup := openrtb_ext.PriceFloorModelGroup{ModelWeight: nil}
+	weight01ModelGroup := openrtb_ext.PriceFloorModelGroup{ModelWeight: getIntPtr(1)}
+	weight25ModelGroup := openrtb_ext.PriceFloorModelGroup{ModelWeight: getIntPtr(25)}
+	weight50ModelGroup := openrtb_ext.PriceFloorModelGroup{ModelWeight: getIntPtr(50)}
+
+	testCases := []struct {
+		name               string
+		ModelGroup         []openrtb_ext.PriceFloorModelGroup
+		fn                 func(int) int
+		expectedModelGroup []openrtb_ext.PriceFloorModelGroup
+	}{
+		{
+			name: "ModelGroup with default weight selection",
+			ModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weightNilModelGroup,
+			},
+			fn: func(i int) int { return 0 },
+			expectedModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight01ModelGroup,
+			},
+		},
+		{
+			name: "ModelGroup with weight = 25 selection",
+			ModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight25ModelGroup,
+				weight50ModelGroup,
+			},
+			fn: func(i int) int { return 5 },
+			expectedModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight25ModelGroup,
+			},
+		},
+		{
+			name: "ModelGroup with weight = 50 selection",
+			ModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight50ModelGroup,
+			},
+			fn: func(i int) int { return 55 },
+			expectedModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight50ModelGroup,
+			},
+		},
+		{
+			name: "ModelGroup with weight = 25 selection",
+			ModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight25ModelGroup,
+				weight50ModelGroup,
+			},
+			fn: func(i int) int { return 80 },
+			expectedModelGroup: []openrtb_ext.PriceFloorModelGroup{
+				weight25ModelGroup,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			resp := selectFloorModelGroup(tc.ModelGroup, tc.fn)
+			assert.Equal(t, resp, tc.expectedModelGroup)
+		})
+	}
+}
+
+func TestGetMinFloorValue(t *testing.T) {
+	rates := map[string]map[string]float64{
+		"USD": {
+			"INR": 81.17,
+		},
+	}
+
+	type args struct {
+		floorExt    *openrtb_ext.PriceFloorRules
+		imp         openrtb2.Imp
+		conversions currency.Conversions
+	}
+	testCases := []struct {
+		name    string
+		args    args
+		want    float64
+		want1   string
+		wantErr error
+	}{
+		{
+			name: "Floor min is available in imp and floor ext",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 2.0, FloorMinCur: "INR", Data: &openrtb_ext.PriceFloorData{Currency: "INR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "INR","floorMin":1.0}}}`)},
+			},
+			want:  1,
+			want1: "INR",
+		},
+		{
+			name: "Floor min and floor min currency is available in imp ext only",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "INR", "floorMin": 1.0}}}`)},
+			},
+			want:  0.0123,
+			want1: "USD",
+		},
+		{
+			name: "Floor min is available in floor ext only",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 1.0, FloorMinCur: "EUR", Data: &openrtb_ext.PriceFloorData{Currency: "EUR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{}}}`)},
+			},
+			want:  1.0,
+			want1: "EUR",
+		},
+		{
+			name: "Floor min is available in floorExt and currency is available in imp",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 2.0, Data: &openrtb_ext.PriceFloorData{Currency: "INR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "INR"}}}`)},
+			},
+			want:  2,
+			want1: "INR",
+		},
+		{
+			name: "Floor min is available in ImpExt and currency is available in floorExt",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMinCur: "USD", Data: &openrtb_ext.PriceFloorData{Currency: "INR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"FloorMin": 2.0}}}`)},
+			},
+			want:  162.34,
+			want1: "INR",
+		},
+		{
+			name: "Floor Min and floor Currency are in Imp and only floor currency is available in floor ext",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMinCur: "USD"},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "USD","floorMin":1.0}}}`)},
+			},
+			want:  1,
+			want1: "USD",
+		},
+		{
+			name: "Currency are different in floor ext and imp",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 0.0, FloorMinCur: "EUR", Data: &openrtb_ext.PriceFloorData{Currency: "INR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "USD","floorMin":1.0}}}`)},
+			},
+			want:  81.17,
+			want1: "INR",
+		},
+		{
+			name: "Floor min is 0 in imp ",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 2.0, FloorMinCur: "JPY", Data: &openrtb_ext.PriceFloorData{Currency: "INR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "USD","floorMin":0.0}}}`)},
+			},
+			want:  162.34,
+			want1: "INR",
+		},
+		{
+			name: "Floor Currency is empty in imp",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMin: 1.0, FloorMinCur: "EUR", Data: &openrtb_ext.PriceFloorData{Currency: "EUR"}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"floors":{"floorMinCur": "","floorMin":-1.0}}}`)},
+			},
+			want:  1.0,
+			want1: "EUR",
+		},
+		{
+			name: "Invalid input",
+			args: args{
+				floorExt: &openrtb_ext.PriceFloorRules{FloorMinCur: "EUR", Data: &openrtb_ext.PriceFloorData{}},
+				imp:      openrtb2.Imp{Ext: json.RawMessage(`{`)},
+			},
+			want:    0.0,
+			want1:   "",
+			wantErr: errors.New("Error in getting FloorMin value : 'unexpected end of JSON input'"),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, got1, err := getMinFloorValue(tc.args.floorExt, &openrtb_ext.ImpWrapper{Imp: &tc.args.imp}, getCurrencyRates(rates))
+			assert.Equal(t, tc.wantErr, err, tc.name)
+			assert.Equal(t, tc.want, got, tc.name)
+			assert.Equal(t, tc.want1, got1, tc.name)
+		})
+	}
+}
+
+func TestSortCombinations(t *testing.T) {
+	type args struct {
+		comb            [][]int
+		numSchemaFields int
+	}
+	tests := []struct {
+		name    string
+		args    args
+		expComb [][]int
+	}{
+		{
+			name: "With schema fields = 3",
+			args: args{
+				comb:            [][]int{{0}, {1}, {2}},
+				numSchemaFields: 3,
+			},
+			expComb: [][]int{{2}, {1}, {0}},
+		},
+		{
+			name: "With schema fields = 3",
+			args: args{
+				comb:            [][]int{{0, 1}, {1, 2}, {0, 2}},
+				numSchemaFields: 3,
+			},
+			expComb: [][]int{{1, 2}, {0, 2}, {0, 1}},
+		},
+		{
+			name: "With schema fields = 4",
+			args: args{
+				comb:            [][]int{{0, 1, 2}, {1, 2, 3}, {0, 2, 3}, {0, 1, 3}},
+				numSchemaFields: 3,
+			},
+			expComb: [][]int{{1, 2, 3}, {0, 2, 3}, {0, 1, 3}, {0, 1, 2}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sortCombinations(tt.args.comb, tt.args.numSchemaFields)
+			assert.Equal(t, tt.expComb, tt.args.comb)
+		})
+	}
+}
+
+func TestGenerateCombinations(t *testing.T) {
+
+	tests := []struct {
+		name            string
+		numSchemaFields int
+		numWildCard     int
+		expComb         [][]int
+	}{
+		{
+			name:            "With schema fields = 3, wildcard = 1",
+			numSchemaFields: 3,
+			numWildCard:     1,
+			expComb:         [][]int{{0}, {1}, {2}},
+		},
+		{
+			name:            "With schema fields = 3, wildcard = 2",
+			numSchemaFields: 3,
+			numWildCard:     2,
+			expComb:         [][]int{{0, 1}, {0, 2}, {1, 2}},
+		},
+		{
+			name:            "With schema fields = 3, wildcard = 3",
+			numSchemaFields: 3,
+			numWildCard:     3,
+			expComb:         [][]int{{0, 1, 2}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotComb := generateCombinations(tt.numSchemaFields, tt.numWildCard)
+			assert.Equal(t, tt.expComb, gotComb)
+		})
+	}
+}
+
+func TestGetDeviceType(t *testing.T) {
+	tests := []struct {
+		name    string
+		request *openrtb2.BidRequest
+		want    string
+	}{
+		{
+			name:    "user agent contains Phone",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Phone Samsung Mobile; Win64; x64)"}},
+			want:    "phone",
+		},
+		{
+			name:    "user agent contains iPhone",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Safari(iPhone Apple Mobile)"}},
+			want:    "phone",
+		},
+		{
+			name:    "user agent contains Mobile.*Android",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Mobile Android; Win64; x64)"}},
+			want:    "phone",
+		},
+		{
+			name:    "user agent contains Android.*Mobile",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Android Redmi Mobile; Win64; x64)"}},
+			want:    "phone",
+		},
+		{
+			name:    "user agent contains Mobile.*Android",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Mobile pixel Android; Win64; x64)"}},
+			want:    "phone",
+		},
+		{
+			name:    "user agent contains Windows NT touch",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Windows NT touch 10.0; Win64; x64)"}},
+			want:    "tablet",
+		},
+		{
+			name:    "user agent contains ipad",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (ipad 13.10; Win64; x64)"}},
+			want:    "tablet",
+		},
+		{
+			name:    "user agent contains Window NT.*touch",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch)"}},
+			want:    "tablet",
+		},
+		{
+			name:    "user agent contains touch.* Window NT",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (touch realme Windows NT Win64; x64)"}},
+			want:    "tablet",
+		},
+		{
+			name:    "user agent contains Android",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Android; Win64; x64)"}},
+			want:    "tablet",
+		},
+		{
+			name:    "user agent not matching phone or tablet",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{UA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}},
+			want:    "desktop",
+		},
+		{
+			name:    "empty user agent",
+			request: &openrtb2.BidRequest{Device: &openrtb2.Device{}},
+			want:    "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getDeviceType(&openrtb_ext.RequestWrapper{BidRequest: tt.request})
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetAdUnitCode(t *testing.T) {
+	tests := []struct {
+		name string
+		imp  *openrtb_ext.ImpWrapper
+		want string
+	}{
+		{
+			name: "imp.ext.gpid",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"gpid":"test_gpid"}`)}},
+			want: "test_gpid",
+		},
+		{
+			name: "imp.TagID",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{TagID: "tag_1"}},
+			want: "tag_1",
+		},
+		{
+			name: "imp.ext.data.pbadslot",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"data":{"pbadslot":"pbslot_1"}}`)}},
+			want: "pbslot_1",
+		},
+		{
+			name: "imp.ext.prebid.storedrequest.id",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid": {"storedrequest":{"id":"123"}}}`)}},
+			want: "123",
+		},
+		{
+			name: "empty adUnitCode",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{}},
+			want: "*",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getAdUnitCode(tt.imp)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetGptSlot(t *testing.T) {
+	tests := []struct {
+		name string
+		imp  *openrtb_ext.ImpWrapper
+		want string
+	}{
+		{
+			name: "imp.ext.data.adserver.adslot",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"data":{"adserver": {"name": "gam", "adslot": "slot_1"}}}`)}},
+			want: "slot_1",
+		},
+		{
+			name: "gptSlot = imp.ext.data.pbadslot",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"data":{"pbadslot":"pbslot_1"}}`)}},
+			want: "pbslot_1",
+		},
+		{
+			name: "empty gptSlot",
+			imp:  &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{}},
+			want: "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getGptSlot(tt.imp)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetSizeValue(t *testing.T) {
+	tests := []struct {
+		name string
+		imp  *openrtb2.Imp
+		want string
+	}{
+		{
+			name: "banner: only one size exists in imp.banner.format",
+			imp:  &openrtb2.Imp{Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}},
+			want: "300x250",
+		},
+		{
+			name: "banner: no imp.banner.format",
+			imp:  &openrtb2.Imp{Banner: &openrtb2.Banner{W: getInt64Ptr(320), H: getInt64Ptr(240)}},
+			want: "320x240",
+		},
+		{
+			name: "video:  imp.video.w and  imp.video.h present",
+			imp:  &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240}},
+			want: "120x240",
+		},
+		{
+			name: "banner: more than one size exists in imp.banner.format",
+			imp:  &openrtb2.Imp{Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 200, H: 300}}}},
+			want: "*",
+		},
+		{
+			name: "Audo creative",
+			imp:  &openrtb2.Imp{Audio: &openrtb2.Audio{}},
+			want: "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getSizeValue(tt.imp)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetMediaType(t *testing.T) {
+	tests := []struct {
+		name string
+		imp  *openrtb2.Imp
+		want string
+	}{
+		{
+			name: "more than one of these: imp.banner, imp.video, imp.native, imp.audio present",
+			imp:  &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240}, Banner: &openrtb2.Banner{W: getInt64Ptr(320), H: getInt64Ptr(240)}},
+			want: "*",
+		},
+		{
+			name: "only banner present",
+			imp:  &openrtb2.Imp{Banner: &openrtb2.Banner{W: getInt64Ptr(320), H: getInt64Ptr(240)}},
+			want: "banner",
+		},
+		{
+			name: "video-outstream present",
+			imp:  &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240, Placement: 2}},
+			want: "video-outstream",
+		},
+		{
+			name: "video-instream present",
+			imp:  &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240, Placement: 1}},
+			want: "video",
+		},
+		{
+			name: "only audio",
+			imp:  &openrtb2.Imp{Audio: &openrtb2.Audio{MinDuration: 10}},
+			want: "audio",
+		},
+		{
+			name: "only native",
+			imp:  &openrtb2.Imp{Native: &openrtb2.Native{Request: "test_req"}},
+			want: "native",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getMediaType(tt.imp)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetSiteDomain(t *testing.T) {
+	type args struct {
+		request *openrtb_ext.RequestWrapper
+	}
+	tests := []struct {
+		name    string
+		request *openrtb_ext.RequestWrapper
+		want    string
+	}{
+		{
+			name:    "Site Domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Domain: "abc.xyz.com"}}},
+			want:    "abc.xyz.com",
+		},
+		{
+			name:    "Site Domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{}}},
+			want:    "*",
+		},
+		{
+			name:    "App Domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{Domain: "cde.rtu.com"}}},
+			want:    "cde.rtu.com",
+		},
+		{
+			name:    "App Domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}},
+			want:    "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getSiteDomain(tt.request)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetPublisherDomain(t *testing.T) {
+	type args struct {
+	}
+	tests := []struct {
+		name    string
+		request *openrtb_ext.RequestWrapper
+		want    string
+	}{
+		{
+			name:    "Site publisher domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Publisher: &openrtb2.Publisher{Domain: "qwe.xyz.com"}}}},
+			want:    "qwe.xyz.com",
+		},
+		{
+			name:    "Site publisher domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Publisher: &openrtb2.Publisher{}}}},
+			want:    "*",
+		},
+		{
+			name:    "App publisher domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{Publisher: &openrtb2.Publisher{Domain: "xyz.com"}}}},
+			want:    "xyz.com",
+		},
+		{
+			name:    "App publisher domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}},
+			want:    "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getPublisherDomain(tt.request)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestGetDomain(t *testing.T) {
+	type args struct {
+	}
+	tests := []struct {
+		name    string
+		request *openrtb_ext.RequestWrapper
+		want    string
+	}{
+		{
+			name:    "Site domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Domain: "qwe.xyz.com", Publisher: &openrtb2.Publisher{Domain: "abc.xyz.com"}}}},
+			want:    "qwe.xyz.com",
+		},
+		{
+			name:    "Site publisher domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Publisher: &openrtb2.Publisher{Domain: "abc.xyz.com"}}}},
+			want:    "abc.xyz.com",
+		},
+		{
+			name:    "Site domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{Publisher: &openrtb2.Publisher{}}}},
+			want:    "*",
+		},
+		{
+			name:    "App publisher domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{Domain: "abc.com", Publisher: &openrtb2.Publisher{Domain: "xyz.com"}}}},
+			want:    "abc.com",
+		},
+		{
+			name:    "App domain present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{Publisher: &openrtb2.Publisher{Domain: "xyz.com"}}}},
+			want:    "xyz.com",
+		},
+		{
+			name:    "App domain not present",
+			request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}},
+			want:    "*",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getDomain(tt.request)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func getIntPtr(v int) *int {
+	return &v
+}
+
+func getInt64Ptr(v int64) *int64 {
+	return &v
+}
diff --git a/floors/validate_test.go b/floors/validate_test.go
new file mode 100644
index 00000000..96dad819
--- /dev/null
+++ b/floors/validate_test.go
@@ -0,0 +1,391 @@
+package floors
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValidateFloorParams(t *testing.T) {
+
+	tt := []struct {
+		name     string
+		floorExt *openrtb_ext.PriceFloorRules
+		Err      error
+	}{
+		{
+			name: "Valid Skip Rate",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com":              1.01,
+						"banner|300x250|*":                            2.01,
+						"banner|300x600|www.website.com|www.test.com": 3.01,
+						"banner|300x600|*":                            4.01,
+					}, Default: 0.01},
+				}}},
+		},
+		{
+			name:     "Invalid Skip Rate at Root level",
+			floorExt: &openrtb_ext.PriceFloorRules{SkipRate: -10},
+			Err:      errors.New("Invalid SkipRate = '-10' at ext.prebid.floors.skiprate"),
+		},
+		{
+			name: "Invalid Skip Rate at Data level",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				SkipRate: -10,
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"*|*|www.website.com": 15.01,
+						"*|*|*":               16.01,
+					}, Default: 0.01},
+				}}},
+			Err: errors.New("Invalid SkipRate = '-10' at ext.prebid.floors.data.skiprate"),
+		},
+		{
+			name:     "Invalid FloorMin ",
+			floorExt: &openrtb_ext.PriceFloorRules{FloorMin: -10},
+			Err:      errors.New("Invalid FloorMin = '-10', value should be >= 0"),
+		},
+		{
+			name: "Invalid FloorSchemaVersion ",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				FloorsSchemaVersion: "1",
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+
+					Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"banner|300x600|*":               4.01,
+					}, Default: 0.01},
+				}}},
+			Err: errors.New("Invalid FloorsSchemaVersion = '1', supported version 2"),
+		},
+	}
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			actErr := validateFloorParams(tc.floorExt)
+			assert.Equal(t, actErr, tc.Err, tc.name)
+		})
+	}
+}
+
+func TestSelectValidFloorModelGroups(t *testing.T) {
+
+	tt := []struct {
+		name     string
+		floorExt *openrtb_ext.PriceFloorRules
+		account  config.Account
+		Err      []error
+	}{
+		{
+			name: "Invalid Skip Rate in model Group 1",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       100,
+					MaxSchemaDims: 5,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelWeight:  getIntPtr(50),
+					SkipRate:     110,
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"banner|300x250|*":               2.01,
+						"banner|300x600|www.website.com": 3.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}, Default: 0.01},
+					{
+						ModelWeight:  getIntPtr(50),
+						SkipRate:     20,
+						ModelVersion: "Version 2",
+						Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+						Values: map[string]float64{
+							"banner|300x250|www.website.com": 1.01,
+							"banner|300x250|*":               2.01,
+							"*|*|www.website.com":            15.01,
+							"*|*|*":                          16.01,
+						}, Default: 0.01},
+				}}},
+			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to SkipRate = '110' is out of range (1-100)")},
+		},
+		{
+			name: "Invalid model weight Model Group 1",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       100,
+					MaxSchemaDims: 5,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelWeight:  getIntPtr(-1),
+					SkipRate:     10,
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"banner|300x250|*":               2.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}, Default: 0.01},
+					{
+						ModelWeight:  getIntPtr(50),
+						SkipRate:     20,
+						ModelVersion: "Version 2",
+						Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+						Values: map[string]float64{
+							"banner|300x250|www.website.com": 1.01,
+							"banner|300x250|*":               2.01,
+							"*|728x90|*":                     14.01,
+							"*|*|www.website.com":            15.01,
+							"*|*|*":                          16.01,
+						}, Default: 0.01},
+				}}},
+			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to ModelWeight = '-1' is out of range (1-100)")},
+		},
+		{
+			name: "Invalid Default Value",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       100,
+					MaxSchemaDims: 5,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelWeight:  getIntPtr(50),
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}, Default: -1.0000},
+				}}},
+			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to Default = '-1' is less than 0")},
+		},
+		{
+			name: "Invalid Number of Schema dimensions",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       100,
+					MaxSchemaDims: 2,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}},
+				}}},
+			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to number of schema fields = '3' are greater than limit 2")},
+		},
+		{
+			name: "Invalid Schema field creativeType",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       100,
+					MaxSchemaDims: 3,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"creativeType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}},
+				}}},
+			Err: []error{errors.New("Invalid schema dimension provided = 'creativeType' in Schema Fields = '[creativeType size domain]'")},
+		},
+		{
+			name: "Invalid Number of rules",
+			account: config.Account{
+				PriceFloors: config.AccountPriceFloors{
+					MaxRule:       3,
+					MaxSchemaDims: 5,
+				},
+			},
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}},
+				}}},
+			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to number of rules = '4' are greater than limit 3")},
+		},
+		{
+			name: "No Modelgroup present",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{}}},
+			Err: []error{errors.New("No model group present in floors.data")},
+		},
+	}
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			_, ErrList := selectValidFloorModelGroups(tc.floorExt.Data.ModelGroups, tc.account)
+			assert.Equal(t, ErrList, tc.Err, tc.name)
+		})
+	}
+}
+
+func TestValidateFloorRulesAndLowerValidRuleKey(t *testing.T) {
+
+	tt := []struct {
+		name         string
+		floorExt     *openrtb_ext.PriceFloorRules
+		Err          []error
+		expctedFloor map[string]float64
+	}{
+		{
+			name: "Invalid floor rule banner|300x600|www.website.com|www.test.com",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
+					Values: map[string]float64{
+						"BANNER|300x250|WWW.WEBSITE.COM":              1.01,
+						"banner|300x250|*":                            2.01,
+						"banner|300x600|www.website.com|www.test.com": 3.01,
+						"banner|300x600|*":                            4.01,
+						"banner|728x90|www.website.com":               5.01,
+						"banner|728x90|*":                             6.01,
+						"banner|*|www.website.com":                    7.01,
+						"banner|*|*":                                  8.01,
+						"*|300x250|www.website.com":                   9.01,
+						"*|300x250|*":                                 10.01,
+						"*|300x600|www.website.com":                   11.01,
+						"*|300x600|*":                                 12.01,
+						"*|728x90|www.website.com":                    13.01,
+						"*|728x90|*":                                  14.01,
+						"*|*|www.website.com":                         15.01,
+						"*|*|*":                                       16.01,
+					}, Default: 0.01},
+				}}},
+			Err: []error{errors.New("Invalid Floor Rule = 'banner|300x600|www.website.com|www.test.com' for Schema Fields = '[mediaType size domain]'")},
+			expctedFloor: map[string]float64{
+				"banner|300x250|www.website.com": 1.01,
+				"banner|300x250|*":               2.01,
+				"banner|300x600|*":               4.01,
+				"banner|728x90|www.website.com":  5.01,
+				"banner|728x90|*":                6.01,
+				"banner|*|www.website.com":       7.01,
+				"banner|*|*":                     8.01,
+				"*|300x250|www.website.com":      9.01,
+				"*|300x250|*":                    10.01,
+				"*|300x600|www.website.com":      11.01,
+				"*|300x600|*":                    12.01,
+				"*|728x90|www.website.com":       13.01,
+				"*|728x90|*":                     14.01,
+				"*|*|www.website.com":            15.01,
+				"*|*|*":                          16.01,
+			},
+		},
+		{
+			name: "Invalid floor rule banner|300x600|www.website.com|www.test.com",
+			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
+				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
+					ModelVersion: "Version 1",
+					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
+					Values: map[string]float64{
+						"banner|300x250|www.website.com": 1.01,
+						"banner|300x250|*":               2.01,
+						"banner|300x600|www.website.com": 3.01,
+						"banner|300x600":                 4.01,
+						"banner|728x90|www.website.com":  5.01,
+						"banner|728x90|*":                6.01,
+						"banner|*|www.website.com":       7.01,
+						"banner|*|*":                     8.01,
+						"*|300x250|www.website.com":      9.01,
+						"*|300x250|*":                    10.01,
+						"*|300x600|www.website.com":      11.01,
+						"*|300x600|*":                    12.01,
+						"*|728x90|www.website.com":       13.01,
+						"*|728x90|*":                     14.01,
+						"*|*|www.website.com":            15.01,
+						"*|*|*":                          16.01,
+					}, Default: 0.01},
+				}}},
+			Err: []error{errors.New("Invalid Floor Rule = 'banner|300x600' for Schema Fields = '[mediaType size domain]'")},
+			expctedFloor: map[string]float64{
+				"banner|300x250|www.website.com": 1.01,
+				"banner|300x250|*":               2.01,
+				"banner|300x600|www.website.com": 3.01,
+				"banner|728x90|www.website.com":  5.01,
+				"banner|728x90|*":                6.01,
+				"banner|*|www.website.com":       7.01,
+				"banner|*|*":                     8.01,
+				"*|300x250|www.website.com":      9.01,
+				"*|300x250|*":                    10.01,
+				"*|300x600|www.website.com":      11.01,
+				"*|300x600|*":                    12.01,
+				"*|728x90|www.website.com":       13.01,
+				"*|728x90|*":                     14.01,
+				"*|*|www.website.com":            15.01,
+				"*|*|*":                          16.01,
+			},
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			ErrList := validateFloorRulesAndLowerValidRuleKey(tc.floorExt.Data.ModelGroups[0].Schema, tc.floorExt.Data.ModelGroups[0].Schema.Delimiter, tc.floorExt.Data.ModelGroups[0].Values)
+			assert.Equal(t, ErrList, tc.Err, tc.name)
+			assert.Equal(t, tc.floorExt.Data.ModelGroups[0].Values, tc.expctedFloor, tc.name)
+		})
+	}
+}
+
+func TestValidateSchemaDimensions(t *testing.T) {
+	tests := []struct {
+		name   string
+		fields []string
+		err    error
+	}{
+		{
+			name:   "valid_fields",
+			fields: []string{"deviceType", "size"},
+		},
+		{
+			name:   "invalid_fields",
+			fields: []string{"deviceType", "dealType"},
+			err:    fmt.Errorf("Invalid schema dimension provided = 'dealType' in Schema Fields = '[deviceType dealType]'"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := validateSchemaDimensions(tt.fields)
+			assert.Equal(t, tt.err, err)
+		})
+	}
+}
diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go
index 110eb2df..ae405f5f 100644
--- a/openrtb_ext/request_wrapper_test.go
+++ b/openrtb_ext/request_wrapper_test.go
@@ -963,14 +963,24 @@ func TestImpWrapperGetImpExt(t *testing.T) {
 		},
 		{
 			description:  "Populated - Ext",
-			givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":42, "tid": "test-tid"}`)}},
+			givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":42,"tid":"test-tid","gpid":"test-gpid","data":{"adserver":{"name":"ads","adslot":"adslot123"},"pbadslot":"pbadslot123"}}`)}},
 			expectedImpExt: ImpExt{
 				ext: map[string]json.RawMessage{
 					"prebid": json.RawMessage(`{"is_rewarded_inventory":1}`),
 					"other":  json.RawMessage(`42`),
 					"tid":    json.RawMessage(`"test-tid"`),
+					"gpid":   json.RawMessage(`"test-gpid"`),
+					"data":   json.RawMessage(`{"adserver":{"name":"ads","adslot":"adslot123"},"pbadslot":"pbadslot123"}`),
+				},
+				tid:  "test-tid",
+				gpId: "test-gpid",
+				data: &ExtImpData{
+					AdServer: &ExtImpDataAdServer{
+						Name:   "ads",
+						AdSlot: "adslot123",
+					},
+					PbAdslot: "pbadslot123",
 				},
-				tid:    "test-tid",
 				prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne},
 			},
 		},