Repository: prebid/prebid-server
Pull request #2354 (2022-08-18 17:41:20)

OpenRTB 2.6: Up + Down Conversions
Implements another part of https://github.com/prebid/prebid-server/issues/2139. The converts are not yet called by the core logic of Prebid Server, but will in a follow-up PR in attempt to keep the reviews smaller / contained.


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a1459626..8b4d73b3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,7 @@ jobs:
   release:
     name: Create Release
     if: github.event.base_ref == 'refs/heads/master'
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     steps:
       - name: Get Version
         id: get_version
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index 570114b0..b56ddad9 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   build:
     name: Trivy
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     steps:
       - name: Checkout Code
         uses: actions/checkout@v2
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index 9cf371ca..63dd7a97 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -6,7 +6,7 @@ on:
 
 jobs:
   validate-merge:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     
     steps:
     - name: Install Go
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index fc36f45c..972d6c0e 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       matrix:
         go-version: [1.16.x, 1.17.x]
-        os: [ubuntu-18.04]
+        os: [ubuntu-20.04]
     runs-on: ${{ matrix.os }}
     
     steps:
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 802806da..50fed350 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -2,6 +2,7 @@ package pubmatic
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -407,8 +408,9 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 			}
 
 			if bidType == openrtb_ext.BidTypeNative {
-				if value, _, _, err := jsonparser.Get([]byte(bid.AdM), string(openrtb_ext.BidTypeNative)); err == nil {
-					bid.AdM = string(value)
+				bid.AdM, err = getNativeAdm(bid.AdM)
+				if err != nil {
+					errs = append(errs, err)
 				}
 			}
 
@@ -424,6 +426,27 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 	return bidResponse, errs
 }
 
+func getNativeAdm(adm string) (string, error) {
+	var err error
+	nativeAdm := make(map[string]interface{})
+	err = json.Unmarshal([]byte(adm), &nativeAdm)
+	if err != nil {
+		return adm, errors.New("unable to unmarshal native adm")
+	}
+
+	// move bid.adm.native to bid.adm
+	if _, ok := nativeAdm["native"]; ok {
+		//using jsonparser to avoid marshaling, encode escape, etc.
+		value, _, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative))
+		if err != nil {
+			return adm, errors.New("unable to get native adm")
+		}
+		adm = string(value)
+	}
+
+	return adm, nil
+}
+
 // getBidType returns the bid type specified in the response bid.ext
 func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType {
 	// setting "banner" as the default bid type
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index d6c4ceaf..fc307de9 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -22,6 +22,7 @@ type BidderInfo struct {
 	GVLVendorID             uint16               `yaml:"gvlVendorID"`
 	Syncer                  *Syncer              `yaml:"userSync"`
 	Experiment              BidderInfoExperiment `yaml:"experiment"`
+	EndpointCompression     string               `yaml:"endpointCompression"` // EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server
 }
 
 // BidderInfoExperiment specifies non-production ready feature config for a bidder
@@ -132,8 +133,8 @@ func (s *Syncer) Override(original *Syncer) *Syncer {
 // In most cases, bidders will specify a URL with a `{{.RedirectURL}}` macro for the call back to
 // Prebid Server and a UserMacro which the bidder server will replace with the user's id. Example:
 //
-//  url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
-//  userMacro: "$UID"
+//	url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
+//	userMacro: "$UID"
 //
 // Prebid Server is configured with a default RedirectURL template matching the /setuid call. This
 // may be overridden for all bidders with the `user_sync.redirect_url` host configuration or for a
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 43dc7a10..9935ea74 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -1403,11 +1403,12 @@ func validateRegs(req *openrtb_ext.RequestWrapper) error {
 	if err != nil {
 		return fmt.Errorf("request.regs.ext is invalid: %v", err)
 	}
-	regExt := regsExt.GetExt()
-	gdprJSON, hasGDPR := regExt["gdpr"]
-	if hasGDPR && (string(gdprJSON) != "0" && string(gdprJSON) != "1") {
-		return errors.New("request.regs.ext.gdpr must be either 0 or 1.")
+
+	gdpr := regsExt.GetGDPR()
+	if gdpr != nil && *gdpr != 0 && *gdpr != 1 {
+		return errors.New("request.regs.ext.gdpr must be either 0 or 1")
 	}
+
 	return nil
 }
 
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
index 1847dc72..03e789ee 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
@@ -44,5 +44,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n"
+  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
index 4a315911..dc15410a 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
@@ -44,5 +44,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n"
+  "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n"
 }
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index c3d5f0ec..e95978b6 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -19,7 +19,7 @@ func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.
 	exchangeBidders := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bidders))
 	for bidderName, bidder := range bidders {
 		info := infos[string(bidderName)]
-		exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
+		exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug, info.EndpointCompression)
 		exchangeBidder = addValidatedBidderMiddleware(exchangeBidder)
 		exchangeBidders[bidderName] = exchangeBidder
 	}
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 51cdc9ac..479c0256 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -2,6 +2,7 @@ package exchange
 
 import (
 	"bytes"
+	"compress/gzip"
 	"context"
 	"crypto/tls"
 	"encoding/json"
@@ -11,6 +12,7 @@ import (
 	"net/http"
 	"net/http/httptrace"
 	"regexp"
+	"strings"
 	"time"
 
 	"github.com/golang/glog"
@@ -55,7 +57,7 @@ type AdaptedBidder interface {
 	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error)
 }
 
-//bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
+// bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
 type bidRequestOptions struct {
 	accountDebugAllowed bool
 	headerDebugAllowed  bool
@@ -106,20 +108,26 @@ type pbsOrtbSeatBid struct {
 	seat string
 }
 
+// Possible values of compression types Prebid Server can support for bidder compression
+const (
+	Gzip string = "GZIP"
+)
+
 // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder.
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) AdaptedBidder {
+func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder {
 	return &bidderAdapter{
 		Bidder:     bidder,
 		BidderName: name,
 		Client:     client,
 		me:         me,
 		config: bidderAdapterConfig{
-			Debug:              cfg.Debug,
-			DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics,
-			DebugInfo:          config.DebugInfo{Allow: parseDebugInfo(debugInfo)},
+			Debug:               cfg.Debug,
+			DisableConnMetrics:  cfg.Metrics.Disabled.AdapterConnectionMetrics,
+			DebugInfo:           config.DebugInfo{Allow: parseDebugInfo(debugInfo)},
+			EndpointCompression: endpointCompression,
 		},
 	}
 }
@@ -140,9 +148,10 @@ type bidderAdapter struct {
 }
 
 type bidderAdapterConfig struct {
-	Debug              config.Debug
-	DisableConnMetrics bool
-	DebugInfo          config.DebugInfo
+	Debug               config.Debug
+	DisableConnMetrics  bool
+	DebugInfo           config.DebugInfo
+	EndpointCompression string
 }
 
 func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
@@ -504,7 +513,16 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 }
 
 func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo {
-	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body))
+	var requestBody []byte
+
+	switch strings.ToUpper(bidder.config.EndpointCompression) {
+	case Gzip:
+		requestBody = compressToGZIP(req.Body)
+		req.Headers.Set("Content-Encoding", "gzip")
+	default:
+		requestBody = req.Body
+	}
+	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody))
 	if err != nil {
 		return &httpCallInfo{
 			request: req,
@@ -675,3 +693,11 @@ func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo
 	}
 	return respData
 }
+
+func compressToGZIP(requestBody []byte) []byte {
+	var b bytes.Buffer
+	w := gzip.NewWriter(&b)
+	w.Write([]byte(requestBody))
+	w.Close()
+	return b.Bytes()
+}
diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go
new file mode 100644
index 00000000..95f8a948
--- /dev/null
+++ b/openrtb_ext/convert_down.go
@@ -0,0 +1,147 @@
+package openrtb_ext
+
+func ConvertDownTo25(r *RequestWrapper) error {
+	// schain
+	if err := moveSupplyChainFrom26To25(r); err != nil {
+		return err
+	}
+
+	// gdpr
+	if err := moveGDPRFrom26To25(r); err != nil {
+		return err
+	}
+	if err := moveConsentFrom26To25(r); err != nil {
+		return err
+	}
+
+	// ccpa
+	if err := moveUSPrivacyFrom26To25(r); err != nil {
+		return err
+	}
+
+	// eid
+	if err := moveEIDFrom26To25(r); err != nil {
+		return err
+	}
+
+	// Do not remove new OpenRTB 2.6 fields. The spec specifies that bidders and exchanges
+	// must tolerate new or unexpected fields gracefully, either ignoring them or treating
+	// them as unknown.
+
+	return nil
+}
+
+// moveSupplyChainFrom26To25 modifies the request to move the OpenRTB 2.6 supply chain
+// object (req.source.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the
+// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 location
+// is expected to be empty.
+func moveSupplyChainFrom26To25(r *RequestWrapper) error {
+	if r.Source == nil || r.Source.SChain == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	schain26 := r.Source.SChain
+	r.Source.SChain = nil
+
+	// move to 2.5 location
+	sourceExt, err := r.GetSourceExt()
+	if err != nil {
+		return err
+	}
+	sourceExt.SetSChain(schain26)
+
+	return nil
+}
+
+// moveGDPRFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR signal
+// field (req.regs.gdpr) to the OpenRTB 2.5 location (req.regs.ext.gdpr). If the
+// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveGDPRFrom26To25(r *RequestWrapper) error {
+	if r.Regs == nil || r.Regs.GDPR == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	gdpr26 := r.Regs.GDPR
+	r.Regs.GDPR = nil
+
+	// move to 2.5 location
+	regExt, err := r.GetRegExt()
+	if err != nil {
+		return err
+	}
+	regExt.SetGDPR(gdpr26)
+
+	return nil
+}
+
+// moveConsentFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR consent
+// field (req.user.consent) to the OpenRTB 2.5 location (req.user.ext.consent). If
+// the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveConsentFrom26To25(r *RequestWrapper) error {
+	if r.User == nil || len(r.User.Consent) == 0 {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	consent26 := r.User.Consent
+	r.User.Consent = ""
+
+	// move to 2.5 location
+	userExt, err := r.GetUserExt()
+	if err != nil {
+		return err
+	}
+	userExt.SetConsent(&consent26)
+
+	return nil
+}
+
+// moveUSPrivacyFrom26To25 modifies the request to move the OpenRTB 2.6 US Privacy (CCPA)
+// consent string (req.regs.us_privacy) to the OpenRTB 2.5 location (req.regs.ext.us_privacy).
+// If the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveUSPrivacyFrom26To25(r *RequestWrapper) error {
+	if r.Regs == nil || len(r.Regs.USPrivacy) == 0 {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	usprivacy26 := r.Regs.USPrivacy
+	r.Regs.USPrivacy = ""
+
+	// move to 2.5 location
+	regExt, err := r.GetRegExt()
+	if err != nil {
+		return err
+	}
+	regExt.SetUSPrivacy(usprivacy26)
+
+	return nil
+}
+
+// moveEIDFrom26To25 modifies the request to move the OpenRTB 2.6 external identifiers
+// (req.user.eids) to the OpenRTB 2.5 location (req.user.ext.eids). If the OpenRTB 2.5
+// location is already present it may be overwritten. The OpenRTB 2.5 location is
+// expected to be empty.
+func moveEIDFrom26To25(r *RequestWrapper) error {
+	if r.User == nil || r.User.EIDs == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	eid26 := r.User.EIDs
+	r.User.EIDs = nil
+
+	// move to 2.5 location
+	userExt, err := r.GetUserExt()
+	if err != nil {
+		return err
+	}
+	userExt.SetEid(&eid26)
+
+	return nil
+}
diff --git a/openrtb_ext/convert_up.go b/openrtb_ext/convert_up.go
new file mode 100644
index 00000000..6ecf0266
--- /dev/null
+++ b/openrtb_ext/convert_up.go
@@ -0,0 +1,150 @@
+package openrtb_ext
+
+import (
+	"fmt"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+)
+
+func ConvertUpTo26(r *RequestWrapper) error {
+	if err := convertUpEnsureExt(r); err != nil {
+		return err
+	}
+
+	// schain
+	moveSupplyChainFrom24To25(r)
+	moveSupplyChainFrom25To26(r)
+
+	// gdpr
+	moveGDPRFrom25To26(r)
+	moveConsentFrom25To26(r)
+
+	// ccpa
+	moveUSPrivacyFrom25To26(r)
+
+	// eid
+	moveEIDFrom25To26(r)
+
+	return nil
+}
+
+// convertUpEnsureExt gets all extension objects required for migration to verify there
+// are no access errors.
+func convertUpEnsureExt(r *RequestWrapper) error {
+	if _, err := r.GetRequestExt(); err != nil {
+		return fmt.Errorf("req.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetSourceExt(); err != nil {
+		return fmt.Errorf("req.source.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetRegExt(); err != nil {
+		return fmt.Errorf("req.regs.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetUserExt(); err != nil {
+		return fmt.Errorf("req.user.ext is invalid: %v", err)
+	}
+
+	return nil
+}
+
+// moveSupplyChainFrom24To25 modifies the request to move the OpenRTB 2.4 supply chain
+// object (req.ext.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the
+// OpenRTB 2.5 location is already present the OpenRTB 2.4 supply chain object is dropped.
+func moveSupplyChainFrom24To25(r *RequestWrapper) {
+	// read and clear 2.4 location
+	reqExt, _ := r.GetRequestExt()
+	schain24 := reqExt.GetSChain()
+	reqExt.SetSChain(nil)
+
+	// move to 2.5 location if not already present
+	sourceExt, _ := r.GetSourceExt()
+	if sourceExt.GetSChain() == nil {
+		sourceExt.SetSChain(schain24)
+	}
+}
+
+// moveSupplyChainFrom25To26 modifies the request to move the OpenRTB 2.5 supply chain
+// object (req.source.ext.schain) to the OpenRTB 2.6 location (req.source.schain). If the
+// OpenRTB 2.6 location is already present the OpenRTB 2.5 supply chain object is dropped.
+func moveSupplyChainFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	sourceExt, _ := r.GetSourceExt()
+	schain25 := sourceExt.GetSChain()
+	sourceExt.SetSChain(nil)
+
+	// move to 2.6 location if not already present
+	if schain25 != nil {
+		// source may be nil if moved indirectly from an OpenRTB 2.4 location, since the ext
+		// is not defined on the source object.
+		if r.Source == nil {
+			r.Source = &openrtb2.Source{}
+		}
+
+		if r.Source.SChain == nil {
+			r.Source.SChain = schain25
+		}
+	}
+}
+
+// moveGDPRFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR signal field
+// (req.regs.ext.gdpr) to the OpenRTB 2.6 location (req.regs.gdpr). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 GDPR signal is dropped.
+func moveGDPRFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	regsExt, _ := r.GetRegExt()
+	gdpr25 := regsExt.GetGDPR()
+	regsExt.SetGDPR(nil)
+
+	// move to 2.6 location
+	if gdpr25 != nil && r.Regs.GDPR == nil {
+		r.Regs.GDPR = gdpr25
+	}
+}
+
+// moveConsentFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR consent field
+// (req.user.ext.consent) to the OpenRTB 2.6 location (req.user.consent). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 GDPR consent is dropped.
+func moveConsentFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	userExt, _ := r.GetUserExt()
+	consent25 := userExt.GetConsent()
+	userExt.SetConsent(nil)
+
+	// move to 2.6 location
+	if consent25 != nil && r.User.Consent == "" {
+		r.User.Consent = *consent25
+	}
+}
+
+// moveUSPrivacyFrom25To26 modifies the request to move the OpenRTB 2.5 US Privacy (CCPA)
+// consent string (req.regs.ext.usprivacy) to the OpenRTB 2.6 location (req.regs.usprivacy).
+// If the OpenRTB 2.6 location is already present the OpenRTB 2.5 consent string is dropped.
+func moveUSPrivacyFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	regsExt, _ := r.GetRegExt()
+	usPrivacy25 := regsExt.GetUSPrivacy()
+	regsExt.SetUSPrivacy("")
+
+	// move to 2.6 location
+	if usPrivacy25 != "" && r.Regs.USPrivacy == "" {
+		r.Regs.USPrivacy = usPrivacy25
+	}
+}
+
+// moveEIDFrom25To26 modifies the request to move the OpenRTB 2.5 external identifiers
+// (req.user.ext.eids) to the OpenRTB 2.6 location (req.user.eids). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 external identifiers is dropped.
+func moveEIDFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	userExt, _ := r.GetUserExt()
+	eid25 := userExt.GetEid()
+	userExt.SetEid(nil)
+
+	// move to 2.6 location
+	if eid25 != nil && r.User.EIDs == nil {
+		r.User.EIDs = *eid25
+	}
+}
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index 51ce753d..380d14d1 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -299,7 +299,9 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error {
 	if len(ue.ext) != 0 || ue.Dirty() {
 		return nil
 	}
+
 	ue.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
@@ -458,26 +460,34 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error {
 	if len(re.ext) != 0 || re.Dirty() {
 		return nil
 	}
+
 	re.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &re.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &re.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := re.ext["prebid"]
 	if hasPrebid {
 		re.prebid = &ExtRequestPrebid{}
-		err = json.Unmarshal(prebidJson, re.prebid)
+		if err := json.Unmarshal(prebidJson, re.prebid); err != nil {
+			return err
+		}
 	}
+
 	schainJson, hasSChain := re.ext["schain"]
 	if hasSChain {
 		re.schain = &openrtb2.SupplyChain{}
-		err = json.Unmarshal(schainJson, re.schain)
+		if err := json.Unmarshal(schainJson, re.schain); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (re *RequestExt) marshal() (json.RawMessage, error) {
@@ -587,21 +597,26 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error {
 	if len(de.ext) != 0 || de.Dirty() {
 		return nil
 	}
+
 	de.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &de.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &de.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := de.ext["prebid"]
 	if hasPrebid {
 		de.prebid = &ExtDevicePrebid{}
-		err = json.Unmarshal(prebidJson, de.prebid)
+		if err := json.Unmarshal(prebidJson, de.prebid); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (de *DeviceExt) marshal() (json.RawMessage, error) {
@@ -674,21 +689,26 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error {
 	if len(ae.ext) != 0 || ae.Dirty() {
 		return nil
 	}
+
 	ae.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &ae.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &ae.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := ae.ext["prebid"]
 	if hasPrebid {
 		ae.prebid = &ExtAppPrebid{}
-		err = json.Unmarshal(prebidJson, ae.prebid)
+		if err := json.Unmarshal(prebidJson, ae.prebid); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (ae *AppExt) marshal() (json.RawMessage, error) {
@@ -753,6 +773,8 @@ func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) {
 type RegExt struct {
 	ext            map[string]json.RawMessage
 	extDirty       bool
+	gdpr           *int8
+	gdprDirty      bool
 	usPrivacy      string
 	usPrivacyDirty bool
 }
@@ -761,23 +783,48 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error {
 	if len(re.ext) != 0 || re.Dirty() {
 		return nil
 	}
+
 	re.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &re.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &re.ext); err != nil {
 		return err
 	}
+
+	gdprJson, hasGDPR := re.ext["gdpr"]
+	if hasGDPR {
+		if err := json.Unmarshal(gdprJson, &re.gdpr); err != nil {
+			return errors.New("gdpr must be an integer")
+		}
+	}
+
 	uspJson, hasUsp := re.ext["us_privacy"]
 	if hasUsp {
-		err = json.Unmarshal(uspJson, &re.usPrivacy)
+		if err := json.Unmarshal(uspJson, &re.usPrivacy); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (re *RegExt) marshal() (json.RawMessage, error) {
+	if re.gdprDirty {
+		if re.gdpr != nil {
+			rawjson, err := json.Marshal(re.gdpr)
+			if err != nil {
+				return nil, err
+			}
+			re.ext["gdpr"] = rawjson
+		} else {
+			delete(re.ext, "gdpr")
+		}
+		re.gdprDirty = false
+	}
+
 	if re.usPrivacyDirty {
 		if len(re.usPrivacy) > 0 {
 			rawjson, err := json.Marshal(re.usPrivacy)
@@ -799,7 +846,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
 }
 
 func (re *RegExt) Dirty() bool {
-	return re.extDirty || re.usPrivacyDirty
+	return re.extDirty || re.gdprDirty || re.usPrivacyDirty
 }
 
 func (re *RegExt) GetExt() map[string]json.RawMessage {
@@ -815,6 +862,16 @@ func (re *RegExt) SetExt(ext map[string]json.RawMessage) {
 	re.extDirty = true
 }
 
+func (re *RegExt) GetGDPR() *int8 {
+	gdpr := re.gdpr
+	return gdpr
+}
+
+func (re *RegExt) SetGDPR(gdpr *int8) {
+	re.gdpr = gdpr
+	re.gdprDirty = true
+}
+
 func (re *RegExt) GetUSPrivacy() string {
 	uSPrivacy := re.usPrivacy
 	return uSPrivacy
@@ -840,23 +897,25 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error {
 	if len(se.ext) != 0 || se.Dirty() {
 		return nil
 	}
+
 	se.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &se.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &se.ext); err != nil {
 		return err
 	}
-	AmpJson, hasAmp := se.ext["amp"]
+
+	ampJson, hasAmp := se.ext["amp"]
 	if hasAmp {
-		err = json.Unmarshal(AmpJson, &se.amp)
-		if err != nil {
-			err = errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
+		if err := json.Unmarshal(ampJson, &se.amp); err != nil {
+			return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
 		}
 	}
 
-	return err
+	return nil
 }
 
 func (se *SiteExt) marshal() (json.RawMessage, error) {
@@ -921,20 +980,25 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error {
 	if len(se.ext) != 0 || se.Dirty() {
 		return nil
 	}
+
 	se.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &se.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &se.ext); err != nil {
 		return err
 	}
+
 	schainJson, hasSChain := se.ext["schain"]
 	if hasSChain {
-		err = json.Unmarshal(schainJson, &se.schain)
+		if err := json.Unmarshal(schainJson, &se.schain); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (se *SourceExt) marshal() (json.RawMessage, error) {


Test patch:
diff --git a/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json b/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json
new file mode 100644
index 00000000..a6192f75
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json
@@ -0,0 +1,122 @@
+{
+  "mockBidRequest": {
+      "id": "test-native-request",
+      "imp": [
+          {
+              "id": "test-native-imp",
+              "native": {
+                  "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}"
+              },
+              "ext": {
+                  "bidder": {
+                      "publisherId": "999",
+                      "wrapper": {
+                          "version": 1,
+                          "profile": 5123
+                      }
+                  }
+              }
+          }
+      ],
+      "ext": {
+        "prebid": {}
+      },
+      "device": {
+          "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+      },
+      "site": {
+          "id": "siteID",
+          "publisher": {
+              "id": "1234"
+          }
+      }
+  },
+  "httpCalls": [
+      {
+          "expectedRequest": {
+              "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+              "body": {
+                  "id": "test-native-request",
+                  "imp": [
+                      {
+                          "id": "test-native-imp",
+                          "native": {
+                              "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}"
+                          }
+                      }
+                  ],
+                  "device": {
+                      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+                  },
+                  "site": {
+                      "id": "siteID",
+                      "publisher": {
+                          "id": "999"
+                      }
+                  },
+                  "ext": {
+                      "wrapper": {
+                          "profile": 5123,
+                          "version": 1
+                      }
+                  }
+              }
+          },
+          "mockResponse": {
+              "status": 200,
+              "body": {
+                  "id": "test-native-request",
+                  "seatbid": [
+                      {
+                          "seat": "958",
+                          "bid": [
+                              {
+                                "id": "test-native-invalid-adm-json",
+                                  "impid": "test-native-imp-invalid-adm",
+                                  "price": 0.500000,
+                                  "adid": "some-test-id",
+                                  "adm": "{",
+                                  "crid": "29681110",
+                                  "ext": {
+                                      "deal_channel": 1,
+                                      "bidtype": 2
+                                  }
+                              }
+                          ]
+                      }
+                  ],
+                  "bidid": "5778926625248726496",
+                  "cur": "USD"
+              }
+          }
+      }
+  ],
+  "expectedBidResponses": [
+      {
+          "currency": "USD",
+          "bids": [
+              {
+                "bid": {
+                    "id": "test-native-invalid-adm-json",
+                    "impid": "test-native-imp-invalid-adm",
+                    "price": 0.5,
+                    "adid": "some-test-id",
+                    "adm": "{",
+                    "crid": "29681110",
+                    "ext": {
+                        "deal_channel": 1,
+                        "bidtype": 2
+                    }
+                },
+                "type": "native"
+              }
+          ]
+      }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unable to unmarshal native adm",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go
index df5b8591..b59f87dc 100644
--- a/config/bidderinfo_test.go
+++ b/config/bidderinfo_test.go
@@ -15,6 +15,42 @@ maintainer:
   email: "some-email@domain.com"
 gvlVendorID: 42
 `
+const fullBidderYAMLConfig = `
+maintainer:
+  email: "some-email@domain.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
+modifyingVastXmlAllowed: true
+debug:
+  allow: true
+gvlVendorID: 42
+userSync:
+  iframe:
+    url: "someURL"
+    userMacro: "aValue"
+  redirect:
+    url: "anotherURL"
+    userMacro: "anotherValue"
+  key: "aKey"
+  supports:
+    - item
+    - item2
+  externalUrl: oneMoreUrl
+  supportCors: true
+experiment:
+  adsCert:
+    enabled: true
+endpointCompression: "GZIP"
+`
 
 func TestLoadBidderInfoFromDisk(t *testing.T) {
 	bidder := "someBidder"
@@ -344,3 +380,48 @@ func TestToGVLVendorIDMap(t *testing.T) {
 	result := givenBidderInfos.ToGVLVendorIDMap()
 	assert.Equal(t, expectedGVLVendorIDMap, result)
 }
+
+func TestReadFullYamlBidderConfig(t *testing.T) {
+	bidder := "someBidder"
+	trueValue := true
+	r := fakeInfoReader{fullBidderYAMLConfig, nil}
+	actualBidderInfo, err := loadBidderInfo(r, map[string]Adapter{strings.ToLower(bidder): {}}, []string{bidder})
+	assert.NoError(t, err, "Error wasn't expected")
+
+	expectedBidderInfo := BidderInfos{
+		bidder: {
+			Enabled: true,
+			Maintainer: &MaintainerInfo{
+				Email: "some-email@domain.com",
+			},
+			GVLVendorID: 42,
+			Capabilities: &CapabilitiesInfo{
+				App: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative},
+				},
+				Site: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative},
+				},
+			},
+			Debug:                   &DebugInfo{Allow: true},
+			ModifyingVastXmlAllowed: true,
+			Syncer: &Syncer{
+				Key: "aKey",
+				IFrame: &SyncerEndpoint{
+					URL:       "someURL",
+					UserMacro: "aValue",
+				},
+				Redirect: &SyncerEndpoint{
+					URL:       "anotherURL",
+					UserMacro: "anotherValue",
+				},
+				Supports:    []string{"item", "item2"},
+				SupportCORS: &trueValue,
+				ExternalURL: "oneMoreUrl",
+			},
+			Experiment:          BidderInfoExperiment{AdsCert: BidderAdsCert{Enabled: true}},
+			EndpointCompression: "GZIP",
+		},
+	}
+	assert.Equalf(t, expectedBidderInfo, actualBidderInfo, "Bidder info objects aren't matching")
+}
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 38497581..c5410cbd 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -1185,7 +1185,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid
 		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL}
 		bidderName := openrtb_ext.BidderName(mockBidder.BidderName)
 
-		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil)
+		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "")
 		mockBidServersArray = append(mockBidServersArray, bidServer)
 	}
 
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index c4949663..59b58f08 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -26,12 +26,12 @@ func TestBuildAdapters(t *testing.T) {
 
 	appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
 	appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled)
-	appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
+	appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil, "")
 	appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
 
 	rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{})
 	rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled)
-	rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
+	rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil, "")
 	rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted)
 
 	testCases := []struct {
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 2d5d4e69..2da7c5a2 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -93,7 +93,7 @@ func TestSingleBidder(t *testing.T) {
 		}
 		bidderImpl.bidResponse = mockBidderResponse
 
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -159,6 +159,132 @@ func TestSingleBidder(t *testing.T) {
 	}
 }
 
+func TestSingleBidderGzip(t *testing.T) {
+	type aTest struct {
+		debugInfo    *config.DebugInfo
+		httpCallsLen int
+	}
+
+	testCases := []*aTest{
+		{&config.DebugInfo{Allow: false}, 0},
+		{&config.DebugInfo{Allow: true}, 1},
+	}
+
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidAdjustments := map[string]float64{"test": 2.0}
+	firstInitialPrice := 3.0
+	secondInitialPrice := 4.0
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte(`{"key":"val"}`),
+			Headers: http.Header{},
+		},
+		bidResponse: nil,
+	}
+
+	ctx := context.Background()
+
+	for _, test := range testCases {
+		mockBidderResponse := &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						Price: firstInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+				},
+				{
+					Bid: &openrtb2.Bid{
+						Price: secondInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+				},
+			},
+		}
+		bidderImpl.bidResponse = mockBidderResponse
+
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "GZIP")
+		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: "test",
+		}
+		bidReqOptions := bidRequestOptions{
+			accountDebugAllowed: true,
+			headerDebugAllowed:  false,
+			addCallSignHeader:   false,
+			bidAdjustments:      bidAdjustments,
+		}
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
+
+		// Make sure the goodSingleBidder was called with the expected arguments.
+		if bidderImpl.httpResponse == nil {
+			t.Errorf("The Bidder should be called with the server's response.")
+		}
+		if bidderImpl.httpResponse.StatusCode != respStatus {
+			t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
+		}
+		if string(bidderImpl.httpResponse.Body) != respBody {
+			t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
+		}
+
+		// Make sure the returned values are what we expect
+		if len(errortypes.FatalOnly(errs)) != 0 {
+			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
+		}
+
+		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
+			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
+		}
+		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
+			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
+		}
+		for index, typedBid := range mockBidderResponse.Bids {
+			if typedBid.Bid != seatBid.bids[index].bid {
+				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
+			}
+			if typedBid.BidType != seatBid.bids[index].bidType {
+				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+			if typedBid.DealPriority != seatBid.bids[index].dealPriority {
+				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+		}
+		bidAdjustment := bidAdjustments["test"]
+		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
+			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
+		}
+		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
+			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
+		}
+		if len(seatBid.httpCalls) != test.httpCallsLen {
+			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
+		}
+		if test.debugInfo.Allow && len(seatBid.httpCalls) > 0 {
+			assert.Equalf(t, "gzip", seatBid.httpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers")
+			assert.Equalf(t, "{\"key\":\"val\"}", seatBid.httpCalls[0].RequestBody, "Mismatched request bodies")
+		}
+		for index, bid := range seatBid.bids {
+			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
+		}
+	}
+}
+
 func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
 	defer server.Close()
@@ -188,7 +314,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -241,7 +367,7 @@ func TestSetGPCHeader(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -290,7 +416,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -361,7 +487,7 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -727,7 +853,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -892,7 +1018,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 		bidderReq := BidderRequest{
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1065,7 +1191,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -1384,7 +1510,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1500,7 +1626,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 	for _, tc := range testCases {
 
 		bidderImpl := &goodSingleBidderWithStoredBidResp{}
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1536,7 +1662,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1768,7 +1894,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
 
 	// Run requestBid using an http.Client with a mock handler
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2009,7 +2135,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2222,7 +2348,7 @@ func TestExtraBid(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2335,7 +2461,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2440,7 +2566,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2552,7 +2678,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 6c649d30..8ef79e30 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -92,13 +92,13 @@ func TestNewExchange(t *testing.T) {
 // The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error)
 // and check whether the returned request successfully prints any '&' characters as it should
 // To do so, we:
-// 	1) Write the endpoint adapter URL with an '&' character into a new config,Configuration struct
-// 	   as specified in https://github.com/prebid/prebid-server/issues/465
-// 	2) Initialize a new exchange with said configuration
-// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the
-// 	   sample request as specified in https://github.com/prebid/prebid-server/issues/465
-// 	4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
-// 	5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns
+//  1. Write the endpoint adapter URL with an '&' character into a new config,Configuration struct
+//     as specified in https://github.com/prebid/prebid-server/issues/465
+//  2. Initialize a new exchange with said configuration
+//  3. Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the
+//     sample request as specified in https://github.com/prebid/prebid-server/issues/465
+//  4. Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
+//  5. Assert we have no '&' characters in the response that exchange.buildBidResponse returns
 func TestCharacterEscape(t *testing.T) {
 	// 1) Adapter with a '& char in its endpoint property
 	//    https://github.com/prebid/prebid-server/issues/465
@@ -348,7 +348,7 @@ func TestDebugBehaviour(t *testing.T) {
 	for _, test := range testCases {
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}),
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}, ""),
 		}
 
 		bidRequest.Test = test.in.test
@@ -532,8 +532,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 		}
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}),
-			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}),
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}, ""),
+			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}, ""),
 		}
 		// Run test
 		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
@@ -694,7 +694,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 		}
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 		}
 
 		// Set custom rates in extension
@@ -771,7 +771,7 @@ func TestAdapterCurrency(t *testing.T) {
 		categoriesFetcher: nilCategoryFetcher{},
 		bidIDGenerator:    &mockBidIDGenerator{false, false},
 		adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil),
+			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""),
 		},
 	}
 
@@ -1152,7 +1152,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
@@ -1459,7 +1459,7 @@ func TestBidReturnsCreative(t *testing.T) {
 	}
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 95860ae7..0973f705 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -142,7 +142,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServer
 		adapterMap[bidder] = AdaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
-		}, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		}, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	}
 	return adapterMap
 }
diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go
new file mode 100644
index 00000000..2ffd2098
--- /dev/null
+++ b/openrtb_ext/convert_down_test.go
@@ -0,0 +1,354 @@
+package openrtb_ext
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConvertDownTo25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description: "2.6 -> 2.5",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+		},
+		{
+			description: "2.6 -> 2.5 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"other":"otherSource","schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+		},
+		{
+			description: "Malformed - SChain",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - GDPR",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - Consent",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - USPrivacy",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Regs: &openrtb2.Regs{USPrivacy: "3", Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - EID",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				User: &openrtb2.User{EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := ConvertDownTo25(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveSupplyChainFrom26To25(t *testing.T) {
+	var (
+		schain1     = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"}
+		schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Source",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Source Schain",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveSupplyChainFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveGDPRFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Regs",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Regs GDPR",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveGDPRFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveConsentFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - User",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - User Consent",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveConsentFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveUSPrivacyFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Regs",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Regs USPrivacy",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveUSPrivacyFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveEIDFrom26To25(t *testing.T) {
+	var (
+		eid1     = []openrtb2.EID{{Source: "1"}}
+		eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`)
+		eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - User",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - User EIDs",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5 - Empty",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: []openrtb2.EID{}}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveEIDFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go
new file mode 100644
index 00000000..075d4f82
--- /dev/null
+++ b/openrtb_ext/convert_up_test.go
@@ -0,0 +1,381 @@
+package openrtb_ext
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConvertUpTo26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description: "Malformed",
+			givenRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`malformed`),
+			},
+			expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "2.4 -> 2.6",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Ext:  json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`),
+				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+		},
+		{
+			description: "2.4 -> 2.6 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Ext:  json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherExt"}`),
+				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)},
+				User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+		},
+		{
+			description: "2.5 -> 2.6",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+		},
+		{
+			description: "2.5 -> 2.6 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3","other":"otherRegs"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := ConvertUpTo26(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestConvertUpEnsureExt(t *testing.T) {
+	testCases := []struct {
+		description  string
+		givenRequest openrtb2.BidRequest
+		expectedErr  string
+	}{
+		{
+			description:  "Empty",
+			givenRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:  "Ext",
+			givenRequest: openrtb2.BidRequest{Ext: json.RawMessage("malformed")},
+			expectedErr:  "req.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "Source.Ext",
+			givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.source.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "Regs.Ext",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.regs.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "User.Ext",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.user.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := convertUpEnsureExt(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, err, test.description)
+		}
+	}
+}
+
+func TestMoveSupplyChainFrom24To25(t *testing.T) {
+	var (
+		schain1 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.4 Migrated To 2.5 - Source Doesn't Exist",
+			givenRequest:    openrtb2.BidRequest{Ext: schain1},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.4 Migrated To 2.5 - Source Exists",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{}, Ext: schain1},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.4 Dropped",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}, Ext: schain2},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.5 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveSupplyChainFrom24To25(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestConvertSupplyChainFrom25To26(t *testing.T) {
+	var (
+		schain1     = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"}
+		schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveSupplyChainFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveGDPRFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveGDPRFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveConsentFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":2}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveConsentFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveUSPrivacyFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveUSPrivacyFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveEIDFrom25To26(t *testing.T) {
+	var (
+		eid1     = []openrtb2.EID{{Source: "1"}}
+		eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`)
+		eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveEIDFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}