Repository: ovrclk/akash
Pull request #823 (2020-09-21 14:26:07)

feat: sdl v2

codecov[bot]: # [Codecov](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=h1) Report
> Merging [#823](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=desc) into [master](https://codecov.io/gh/ovrclk/akash/commit/1c2db59a484859546c01f7b1e9369e04f1f39ea7?el=desc) will **decrease** coverage by `1.90%`.
> The diff coverage is `48.85%`.

[![Impacted file tree graph](https://codecov.io/gh/ovrclk/akash/pull/823/graphs/tree.svg?width=650&height=150&src=pr&token=QXe3zmrbI2)](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #823      +/-   ##
==========================================
- Coverage   42.48%   40.57%   -1.91%     
==========================================
  Files         157      165       +8     
  Lines        8757     7912     -845     
==========================================
- Hits         3720     3210     -510     
+ Misses       4711     4358     -353     
- Partials      326      344      +18     
```


| [Impacted Files](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [provider/bidengine/pricing.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-cHJvdmlkZXIvYmlkZW5naW5lL3ByaWNpbmcuZ28=) | `0.00% <0.00%> (ø)` | |
| [provider/cluster/kube/builder.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-cHJvdmlkZXIvY2x1c3Rlci9rdWJlL2J1aWxkZXIuZ28=) | `8.67% <0.00%> (-1.33%)` | :arrow_down: |
| [provider/cluster/kube/client.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-cHJvdmlkZXIvY2x1c3Rlci9rdWJlL2NsaWVudC5nbw==) | `0.00% <0.00%> (ø)` | |
| [sdl/expose.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-c2RsL2V4cG9zZS5nbw==) | `42.85% <0.00%> (-12.70%)` | :arrow_down: |
| [sdl/memory.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-c2RsL21lbW9yeS5nbw==) | `0.00% <0.00%> (ø)` | |
| [x/market/types/types.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-eC9tYXJrZXQvdHlwZXMvdHlwZXMuZ28=) | `50.57% <ø> (-1.28%)` | :arrow_down: |
| [x/provider/config/config.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-eC9wcm92aWRlci9jb25maWcvY29uZmlnLmdv) | `50.00% <ø> (-10.00%)` | :arrow_down: |
| [x/provider/types/types.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-eC9wcm92aWRlci90eXBlcy90eXBlcy5nbw==) | `27.77% <ø> (-1.39%)` | :arrow_down: |
| [provider/cluster/client.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-cHJvdmlkZXIvY2x1c3Rlci9jbGllbnQuZ28=) | `6.38% <15.38%> (-4.94%)` | :arrow_down: |
| [types/resource.go](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree#diff-dHlwZXMvcmVzb3VyY2UuZ28=) | `20.72% <20.72%> (-79.28%)` | :arrow_down: |
| ... and [184 more](https://codecov.io/gh/ovrclk/akash/pull/823/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=footer). Last update [1c2db59...e9f8844](https://codecov.io/gh/ovrclk/akash/pull/823?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/_docs/deployment.yaml b/_docs/deployment.yaml
index 01b27d39..1b210315 100644
--- a/_docs/deployment.yaml
+++ b/_docs/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 include:
   - "foo.yaml"
@@ -59,17 +59,29 @@ profiles:
 
   compute:
     web:
-      cpu: "0.01"
-      memory: "128Mi"
-      storage: "512Mi"
+      resources:
+        cpu:
+          units: "0.01"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "512Mi"
     db:
-      cpu: "0.01"
-      memory: "128Mi"
-      storage: "1Gi"
+      resources:
+        cpu:
+          unites: "0.01"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "1Gi"
     db-pool:
-      cpu: "0.01"
-      memory: "128Mi"
-      storage: "512Mi"
+      resources:
+        cpu:
+          units: "0.01"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "512Mi"
 
   placement:
     westcoast:
diff --git a/_docs/examples/provider/deployment.yaml b/_docs/examples/provider/deployment.yaml
index f18db53f..a6f2268e 100644
--- a/_docs/examples/provider/deployment.yaml
+++ b/_docs/examples/provider/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 services:
   web:
     image: quay.io/ovrclk/demo-app
@@ -11,9 +11,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: 0.1
-      memory: 16Mi
-      storage: 128Mi
+      resources:
+        cpu:
+          units: 0.1
+        memory:
+          size: 16Mi
+        storage:
+          size: 128Mi
   placement:
     westcoast:
       attributes:
diff --git a/_run/kube/deployment.yaml b/_run/kube/deployment.yaml
index fe2e5cca..9bf92a9b 100644
--- a/_run/kube/deployment.yaml
+++ b/_run/kube/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   web:
@@ -15,9 +15,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: 0.1
-      memory: 16Mi
-      storage: 128Mi
+      resources:
+        cpu:
+          units: 0.1
+        memory:
+          size: 16Mi
+        storage:
+          size: 128Mi
   placement:
     westcoast:
       attributes:
diff --git a/_run/lite/deployment.yaml b/_run/lite/deployment.yaml
index 6174a1b5..e8b46028 100644
--- a/_run/lite/deployment.yaml
+++ b/_run/lite/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   web:
@@ -13,9 +13,13 @@ profiles:
 
   compute:
     web:
-      cpu: "100m"
-      memory: "128Mi"
-      storage: "256Mi"
+      resources:
+        cpu:
+          units: "100m"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "256Mi"
 
   placement:
     westcoast:
diff --git a/_run/single/deployment.yaml b/_run/single/deployment.yaml
index 399548aa..c4900ea5 100644
--- a/_run/single/deployment.yaml
+++ b/_run/single/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   web:
@@ -15,9 +15,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: 0.1
-      memory: 16Mi
-      storage: 128Mi
+      resources:
+        cpu:
+          units: 0.1
+        memory:
+          size: 16Mi
+        storage:
+          size: 128Mi
   placement:
     westcoast:
       attributes:
diff --git a/go.mod b/go.mod
index 2aaaf026..a23eb9e5 100644
--- a/go.mod
+++ b/go.mod
@@ -40,6 +40,7 @@ require (
 	google.golang.org/appengine v1.6.6-0.20191016204603-16bce7d3dc4e // indirect
 	google.golang.org/grpc v1.31.1
 	gopkg.in/yaml.v2 v2.3.0
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
 	k8s.io/api v0.18.2
 	k8s.io/apimachinery v0.18.2
 	k8s.io/client-go v0.18.2
diff --git a/manifest/types.go b/manifest/types.go
index 475198be..2a2a6b07 100644
--- a/manifest/types.go
+++ b/manifest/types.go
@@ -24,12 +24,12 @@ func (g Group) GetName() string {
 }
 
 // GetResources returns list of resources in a group
-func (g Group) GetResources() []types.Resource {
-	resources := make([]types.Resource, 0, len(g.Services))
+func (g Group) GetResources() []types.Resources {
+	resources := make([]types.Resources, 0, len(g.Services))
 	for _, s := range g.Services {
-		resources = append(resources, types.Resource{
-			Unit:  s.Unit,
-			Count: s.Count,
+		resources = append(resources, types.Resources{
+			Resources: s.Resources,
+			Count:     s.Count,
 		})
 	}
 	return resources
@@ -37,19 +37,19 @@ func (g Group) GetResources() []types.Resource {
 
 // Service stores name, image, args, env, unit, count and expose list of service
 type Service struct {
-	Name    string
-	Image   string
-	Command []string
-	Args    []string
-	Env     []string
-	Unit    types.Unit
-	Count   uint32
-	Expose  []ServiceExpose
+	Name      string
+	Image     string
+	Command   []string
+	Args      []string
+	Env       []string
+	Resources types.ResourceUnits
+	Count     uint32
+	Expose    []ServiceExpose
 }
 
-// GetUnit returns unit of service
-func (s Service) GetUnit() types.Unit {
-	return s.Unit
+// GetResourcesUnit returns resources unit of service
+func (s Service) GetResourceUnits() types.ResourceUnits {
+	return s.Resources
 }
 
 // GetCount returns count of service
diff --git a/pkg/apis/akash.network/v1/types.go b/pkg/apis/akash.network/v1/types.go
index 7a358313..9853c43d 100644
--- a/pkg/apis/akash.network/v1/types.go
+++ b/pkg/apis/akash.network/v1/types.go
@@ -1,11 +1,14 @@
 package v1
 
 import (
+	"math"
 	"strconv"
 
+	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+
 	"github.com/ovrclk/akash/manifest"
 	"github.com/ovrclk/akash/provider/cluster"
 	"github.com/ovrclk/akash/types"
@@ -36,6 +39,8 @@ type ManifestSpec struct {
 	Group   ManifestGroup `json:"group"`
 }
 
+// type ResourceUnits types.ResourceUnits
+
 // Deployment returns the cluster.Deployment that the saved manifest represents.
 func (m Manifest) Deployment() (cluster.Deployment, error) {
 	lid, err := m.Spec.LeaseID.toAkash()
@@ -65,6 +70,11 @@ func (d deployment) ManifestGroup() manifest.Group {
 
 // NewManifest creates new manifest with provided details. Returns error incase of failure.
 func NewManifest(name string, lid mtypes.LeaseID, mgroup *manifest.Group) (*Manifest, error) {
+	group, err := manifestGroupFromAkash(mgroup)
+	if err != nil {
+		return nil, err
+	}
+
 	return &Manifest{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Manifest",
@@ -74,7 +84,7 @@ func NewManifest(name string, lid mtypes.LeaseID, mgroup *manifest.Group) (*Mani
 			Name: name,
 		},
 		Spec: ManifestSpec{
-			Group:   manifestGroupFromAkash(mgroup),
+			Group:   group,
 			LeaseID: leaseIDFromAkash(lid),
 		},
 	}, nil
@@ -153,17 +163,22 @@ func (m ManifestGroup) toAkash() (manifest.Group, error) {
 }
 
 // ManifestGroupFromAkash returns manifest group instance from akash group
-func manifestGroupFromAkash(m *manifest.Group) ManifestGroup {
+func manifestGroupFromAkash(m *manifest.Group) (ManifestGroup, error) {
 	ma := ManifestGroup{
 		Name:     m.Name,
 		Services: make([]ManifestService, 0, len(m.Services)),
 	}
 
 	for _, svc := range m.Services {
-		ma.Services = append(ma.Services, manifestServiceFromAkash(svc))
+		service, err := manifestServiceFromAkash(svc)
+		if err != nil {
+			return ManifestGroup{}, err
+		}
+
+		ma.Services = append(ma.Services, service)
 	}
 
-	return ma
+	return ma, nil
 }
 
 // ManifestService stores name, image, args, env, unit, count and expose list of service
@@ -175,7 +190,8 @@ type ManifestService struct {
 	Args  []string `json:"args,omitempty"`
 	Env   []string `json:"env,omitempty"`
 	// Resource requirements
-	Unit ResourceUnit `json:"unit"`
+	// in current version of CRD it is named as unit
+	Resources ResourceUnits `json:"unit"`
 	// Number of instances
 	Count uint32 `json:"count,omitempty"`
 	// Overlay Network Links
@@ -183,18 +199,19 @@ type ManifestService struct {
 }
 
 func (ms ManifestService) toAkash() (manifest.Service, error) {
-	unit, err := ms.Unit.toAkash()
+	res, err := ms.Resources.toAkash()
 	if err != nil {
 		return manifest.Service{}, err
 	}
+
 	ams := &manifest.Service{
-		Name:   ms.Name,
-		Image:  ms.Image,
-		Args:   ms.Args,
-		Env:    ms.Env,
-		Unit:   unit,
-		Count:  ms.Count,
-		Expose: make([]manifest.ServiceExpose, 0, len(ms.Expose)),
+		Name:      ms.Name,
+		Image:     ms.Image,
+		Args:      ms.Args,
+		Env:       ms.Env,
+		Resources: res,
+		Count:     ms.Count,
+		Expose:    make([]manifest.ServiceExpose, 0, len(ms.Expose)),
 	}
 
 	for _, expose := range ms.Expose {
@@ -204,22 +221,27 @@ func (ms ManifestService) toAkash() (manifest.Service, error) {
 	return *ams, nil
 }
 
-func manifestServiceFromAkash(ams manifest.Service) ManifestService {
+func manifestServiceFromAkash(ams manifest.Service) (ManifestService, error) {
+	resources, err := resourceUnitsFromAkash(ams.Resources)
+	if err != nil {
+		return ManifestService{}, err
+	}
+
 	ms := ManifestService{
-		Name:   ams.Name,
-		Image:  ams.Image,
-		Args:   ams.Args,
-		Env:    ams.Env,
-		Unit:   resourceUnitFromAkash(ams.Unit),
-		Count:  ams.Count,
-		Expose: make([]ManifestServiceExpose, 0, len(ams.Expose)),
+		Name:      ams.Name,
+		Image:     ams.Image,
+		Args:      ams.Args,
+		Env:       ams.Env,
+		Resources: resources,
+		Count:     ams.Count,
+		Expose:    make([]ManifestServiceExpose, 0, len(ams.Expose)),
 	}
 
 	for _, expose := range ams.Expose {
 		ms.Expose = append(ms.Expose, manifestServiceExposeFromAkash(expose))
 	}
 
-	return ms
+	return ms, nil
 }
 
 // ManifestServiceExpose stores exposed ports and accepted hosts details
@@ -255,36 +277,53 @@ func manifestServiceExposeFromAkash(amse manifest.ServiceExpose) ManifestService
 	}
 }
 
-// ResourceUnit stores cpu, memory and storage details
-type ResourceUnit struct {
+// ResourceUnits stores cpu, memory and storage details
+type ResourceUnits struct {
 	CPU     uint32 `json:"cpu,omitempty"`
 	Memory  string `json:"memory,omitempty"`
 	Storage string `json:"storage,omitempty"`
 }
 
-func (ru ResourceUnit) toAkash() (types.Unit, error) {
+func (ru ResourceUnits) toAkash() (types.ResourceUnits, error) {
 	memory, err := strconv.ParseUint(ru.Memory, 10, 64)
 	if err != nil {
-		return types.Unit{}, err
+		return types.ResourceUnits{}, err
 	}
 	storage, err := strconv.ParseUint(ru.Storage, 10, 64)
 	if err != nil {
-		return types.Unit{}, err
+		return types.ResourceUnits{}, err
 	}
 
-	return types.Unit{
-		CPU:     ru.CPU,
-		Memory:  memory,
-		Storage: storage,
+	return types.ResourceUnits{
+		CPU: &types.CPU{
+			Units: types.NewResourceValue(uint64(ru.CPU)),
+		},
+		Memory: &types.Memory{
+			Quantity: types.NewResourceValue(memory),
+		},
+		Storage: &types.Storage{
+			Quantity: types.NewResourceValue(storage),
+		},
 	}, nil
 }
 
-func resourceUnitFromAkash(aru types.Unit) ResourceUnit {
-	return ResourceUnit{
-		CPU:     aru.CPU,
-		Memory:  strconv.FormatUint(aru.Memory, 10),
-		Storage: strconv.FormatUint(aru.Storage, 10),
+func resourceUnitsFromAkash(aru types.ResourceUnits) (ResourceUnits, error) {
+	res := ResourceUnits{}
+	if aru.CPU != nil {
+		if aru.CPU.Units.Value() > math.MaxUint32 {
+			return ResourceUnits{}, errors.Errorf("k8s api: cpu units value overflows uint32")
+		}
+		res.CPU = uint32(aru.CPU.Units.Value())
+	}
+	if aru.Memory != nil {
+		res.Memory = strconv.FormatUint(aru.Memory.Quantity.Value(), 10)
 	}
+
+	if aru.Storage != nil {
+		res.Storage = strconv.FormatUint(aru.Storage.Quantity.Value(), 10)
+	}
+
+	return res, nil
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/apis/akash.network/v1/zz_generated.deepcopy.go b/pkg/apis/akash.network/v1/zz_generated.deepcopy.go
index 58400b7d..5c747c07 100644
--- a/pkg/apis/akash.network/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/akash.network/v1/zz_generated.deepcopy.go
@@ -137,7 +137,7 @@ func (in *ManifestService) DeepCopyInto(out *ManifestService) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
-	out.Unit = in.Unit
+	out.Resources = in.Resources
 	if in.Expose != nil {
 		in, out := &in.Expose, &out.Expose
 		*out = make([]ManifestServiceExpose, len(*in))
@@ -214,17 +214,17 @@ func (in *ManifestStatus) DeepCopy() *ManifestStatus {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ResourceUnit) DeepCopyInto(out *ResourceUnit) {
+func (in *ResourceUnits) DeepCopyInto(out *ResourceUnits) {
 	*out = *in
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceUnit.
-func (in *ResourceUnit) DeepCopy() *ResourceUnit {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceUnits.
+func (in *ResourceUnits) DeepCopy() *ResourceUnits {
 	if in == nil {
 		return nil
 	}
-	out := new(ResourceUnit)
+	out := new(ResourceUnits)
 	in.DeepCopyInto(out)
 	return out
 }
diff --git a/proto/akash/attribute.proto b/proto/akash/attribute.proto
new file mode 100644
index 00000000..6f7597b0
--- /dev/null
+++ b/proto/akash/attribute.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+package akash;
+
+import "gogoproto/gogo.proto";
+
+option (gogoproto.goproto_stringer_all) = false;
+option (gogoproto.stringer_all) = false;
+option go_package = "github.com/ovrclk/akash/types";
+
+message Attribute {
+  option (gogoproto.goproto_getters) = false;
+  string key = 1 [
+    (gogoproto.moretags) = "yaml:\"key\""
+  ];
+  string value = 2 [
+    (gogoproto.moretags) = "yaml:\"value\""
+  ];
+}
diff --git a/proto/akash/deployment/group.proto b/proto/akash/deployment/group.proto
index c02171f2..d96439a6 100644
--- a/proto/akash/deployment/group.proto
+++ b/proto/akash/deployment/group.proto
@@ -2,9 +2,9 @@ syntax = "proto3";
 package akash.deployment;
 
 import "gogoproto/gogo.proto";
+import "akash/attribute.proto";
 import "akash/resource.proto";
 import "cosmos/base/v1beta1/coin.proto";
-import "cosmos/base/abci/v1beta1/abci.proto";
 
 option go_package = "github.com/ovrclk/akash/x/deployment/types";
 
@@ -50,7 +50,7 @@ message GroupSpec {
         (gogoproto.jsontag)    = "name",
         (gogoproto.moretags)   = "yaml:\"name\""
     ];
-    repeated cosmos.base.abci.v1beta1.Attribute requirements = 2 [
+    repeated akash.Attribute requirements = 2 [
         (gogoproto.nullable)   = false,
         (gogoproto.jsontag)    = "requirements",
         (gogoproto.moretags)   = "yaml:\"requirements\""
@@ -114,8 +114,8 @@ message Group {
 
 message Resource {
     option (gogoproto.equal) = true;
-    
-    akash.Unit unit = 1 [
+
+    akash.ResourceUnits resources = 1 [
         (gogoproto.nullable)   = false,
         (gogoproto.jsontag)    = "unit",
         (gogoproto.moretags)   = "yaml:\"unit\""
diff --git a/proto/akash/provider/provider.proto b/proto/akash/provider/provider.proto
index 982d2c64..0c5621dc 100644
--- a/proto/akash/provider/provider.proto
+++ b/proto/akash/provider/provider.proto
@@ -2,7 +2,7 @@ syntax = "proto3";
 package akash.provider;
 
 import "gogoproto/gogo.proto";
-import "cosmos/base/abci/v1beta1/abci.proto";
+import "akash/attribute.proto";
 
 option go_package = "github.com/ovrclk/akash/x/provider/types";
 
@@ -20,7 +20,7 @@ message MsgCreateProvider {
         (gogoproto.jsontag)    = "host-uri",
         (gogoproto.moretags)   = "yaml:\"host-uri\""
     ];
-    repeated cosmos.base.abci.v1beta1.Attribute attributes = 3 [
+    repeated akash.Attribute attributes = 3 [
         (gogoproto.castrepeated) = "Attributes",
         (gogoproto.nullable)   = false,
         (gogoproto.jsontag)    = "attributes",
@@ -42,7 +42,7 @@ message MsgUpdateProvider {
         (gogoproto.jsontag)    = "host-uri",
         (gogoproto.moretags)   = "yaml:\"host-uri\""
     ];
-    repeated cosmos.base.abci.v1beta1.Attribute attributes = 3 [
+    repeated akash.Attribute attributes = 3 [
         (gogoproto.castrepeated) = "Attributes",
         (gogoproto.nullable)   = false,
         (gogoproto.jsontag)    = "attributes",
@@ -76,10 +76,10 @@ message Provider {
         (gogoproto.jsontag)    = "host-uri",
         (gogoproto.moretags)   = "yaml:\"host-uri\""
     ];
-    repeated cosmos.base.abci.v1beta1.Attribute attributes = 3 [
+    repeated akash.Attribute attributes = 3 [
         (gogoproto.castrepeated) = "Attributes",
         (gogoproto.nullable)   = false,
         (gogoproto.jsontag)    = "attributes",
         (gogoproto.moretags)   = "yaml:\"attributes\""
     ];
-}
\ No newline at end of file
+}
diff --git a/proto/akash/resource.proto b/proto/akash/resource.proto
index 910cdffb..224d10b3 100644
--- a/proto/akash/resource.proto
+++ b/proto/akash/resource.proto
@@ -2,24 +2,68 @@ syntax = "proto3";
 package akash;
 
 import "gogoproto/gogo.proto";
+import "akash/attribute.proto";
+import "akash/resourcevalue.proto";
 
 option go_package = "github.com/ovrclk/akash/types";
 
-// Unit stores cpu, memory and storage metrics
-message Unit {
-    option (gogoproto.equal) = true;
+message CPU {
+  option (gogoproto.equal) = true;
+  ResourceValue units = 1 [
+    (gogoproto.nullable) = false
+  ];
+  repeated Attribute attributes = 2 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "attributes,omitempty",
+    (gogoproto.moretags) = "yaml:\"cpu,omitempty\""
+  ];
+}
 
-    uint32 cpu = 1 [
-        (gogoproto.customname) = "CPU",
-        (gogoproto.jsontag)    = "cpu",
-        (gogoproto.moretags)   = "yaml:\"cpu\""
-    ];
-    uint64 memory = 2 [
-        (gogoproto.jsontag)    = "memory",
-        (gogoproto.moretags)   = "yaml:\"memory\""
-    ];
-    uint64 storage = 3 [
-        (gogoproto.jsontag)    = "storage",
-        (gogoproto.moretags)   = "yaml:\"storage\""
-    ];
-}
\ No newline at end of file
+message Memory {
+  option (gogoproto.equal) = true;
+  ResourceValue quantity = 1 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "size",
+    (gogoproto.moretags) = "yaml:\"size\""
+  ];
+  repeated Attribute attributes = 2 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "attributes,omitempty",
+    (gogoproto.moretags) = "yaml:\"cpu,omitempty\""
+  ];
+}
+
+message Storage {
+  option (gogoproto.equal) = true;
+  ResourceValue quantity = 1 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "size",
+    (gogoproto.moretags) = "yaml:\"size\""
+  ];
+  repeated Attribute attributes = 2 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "attributes,omitempty",
+    (gogoproto.moretags) = "yaml:\"cpu,omitempty\""
+  ];
+}
+
+// ResourceUnits describes all available resources types for deployment/node etc
+// if field is nil resource is not present in the given data-structure
+message ResourceUnits {
+  option (gogoproto.equal) = true;
+  CPU CPU = 1 [
+    (gogoproto.nullable) = true,
+    (gogoproto.jsontag) = "cpu,omitempty",
+    (gogoproto.moretags) = "yaml:\"cpu,omitempty\""
+  ];
+  Memory memory = 2 [
+    (gogoproto.nullable) = true,
+    (gogoproto.jsontag) = "memory,omitempty",
+    (gogoproto.moretags) = "yaml:\"memory,omitempty\""
+  ];
+  Storage storage = 3 [
+    (gogoproto.nullable) = true,
+    (gogoproto.jsontag) = "storage,omitempty",
+    (gogoproto.moretags) = "yaml:\"storage,omitempty\""
+  ];
+}
diff --git a/proto/akash/resourcevalue.proto b/proto/akash/resourcevalue.proto
new file mode 100644
index 00000000..b43a77c5
--- /dev/null
+++ b/proto/akash/resourcevalue.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+package akash;
+
+import "gogoproto/gogo.proto";
+
+option go_package = "github.com/ovrclk/akash/types";
+
+// Unit stores cpu, memory and storage metrics
+message ResourceValue {
+  option (gogoproto.equal) = true;
+  bytes val = 1 [
+    (gogoproto.nullable) = false,
+    (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
+  ];
+}
diff --git a/provider/bidengine/pricing.go b/provider/bidengine/pricing.go
index 2832bf77..f5320448 100644
--- a/provider/bidengine/pricing.go
+++ b/provider/bidengine/pricing.go
@@ -42,7 +42,7 @@ func calculatePriceRange(gspec *dtypes.GroupSpec) (sdk.Coin, sdk.Coin) {
 
 	for _, group := range gspec.Resources {
 		mem = mem.Add(
-			sdk.NewIntFromUint64(group.Unit.Memory).
+			sdk.NewIntFromUint64(group.Resources.Memory.Quantity.Value()).
 				MulRaw(int64(group.Count)))
 	}
 
diff --git a/provider/cluster/client.go b/provider/cluster/client.go
index f8d7163c..aaa89b88 100644
--- a/provider/cluster/client.go
+++ b/provider/cluster/client.go
@@ -37,17 +37,18 @@ type Client interface {
 // Node interface predefined with ID and Available methods
 type Node interface {
 	ID() string
-	Available() atypes.Unit
+	Available() atypes.ResourceUnits
+	Reserve(atypes.ResourceUnits) error
 }
 
 type node struct {
-	id        string
-	available atypes.Unit
+	id                 string
+	availableResources atypes.ResourceUnits
 }
 
 // NewNode returns new Node instance with provided details
-func NewNode(id string, available atypes.Unit) Node {
-	return &node{id: id, available: available}
+func NewNode(id string, available atypes.ResourceUnits) Node {
+	return &node{id: id, availableResources: available}
 }
 
 // ID returns id of node
@@ -55,9 +56,13 @@ func (n *node) ID() string {
 	return n.id
 }
 
+func (n *node) Reserve(atypes.ResourceUnits) error {
+	return nil
+}
+
 // Available returns available units of node
-func (n *node) Available() atypes.Unit {
-	return n.available
+func (n *node) Available() atypes.ResourceUnits {
+	return n.availableResources
 }
 
 // Deployment interface defined with LeaseID and ManifestGroup methods
@@ -152,10 +157,16 @@ func (c *nullClient) Deployments(ctx context.Context) ([]Deployment, error) {
 
 func (c *nullClient) Inventory(ctx context.Context) ([]Node, error) {
 	return []Node{
-		NewNode("solo", atypes.Unit{
-			CPU:     nullClientCPU,
-			Memory:  nullClientMemory,
-			Storage: nullClientStorage,
+		NewNode("solo", atypes.ResourceUnits{
+			CPU: &atypes.CPU{
+				Units: atypes.NewResourceValue(nullClientCPU),
+			},
+			Memory: &atypes.Memory{
+				Quantity: atypes.NewResourceValue(nullClientMemory),
+			},
+			Storage: &atypes.Storage{
+				Quantity: atypes.NewResourceValue(nullClientStorage),
+			},
 		}),
 	}, nil
 }
diff --git a/provider/cluster/inventory.go b/provider/cluster/inventory.go
index bcb43ccf..bd98a8f0 100644
--- a/provider/cluster/inventory.go
+++ b/provider/cluster/inventory.go
@@ -5,7 +5,7 @@ import (
 	"errors"
 	"time"
 
-	lifecycle "github.com/boz/go-lifecycle"
+	"github.com/boz/go-lifecycle"
 	"github.com/tendermint/tendermint/libs/log"
 
 	"github.com/ovrclk/akash/provider/event"
@@ -67,8 +67,7 @@ func newInventoryService(
 
 	reservations := make([]*reservation, 0, len(deployments))
 	for _, d := range deployments {
-		reservations = append(reservations,
-			newReservation(d.LeaseID().OrderID(), d.ManifestGroup()))
+		reservations = append(reservations, newReservation(d.LeaseID().OrderID(), d.ManifestGroup()))
 	}
 
 	go is.lc.WatchChannel(donech)
@@ -215,7 +214,6 @@ loop:
 
 		case req := <-is.reservech:
 			// create new registration if capacity available
-
 			reservation := newReservation(req.order, req.resources)
 
 			is.log.Debug("reservation requested", "order", req.order, "resources", req.resources)
@@ -266,7 +264,6 @@ loop:
 			req.ch <- inventoryResponse{err: errNotFound}
 
 		case ch := <-is.statusch:
-
 			ch <- is.getStatus(inventory, reservations)
 
 		case <-t.C:
@@ -320,21 +317,19 @@ func (is *inventoryService) runCheck(ctx context.Context) <-chan runner.Result {
 	})
 }
 
-func (is *inventoryService) getStatus(
-	inventory []Node, reservations []*reservation) InventoryStatus {
-
+func (is *inventoryService) getStatus(inventory []Node, reservations []*reservation) InventoryStatus {
 	status := InventoryStatus{}
+	for _, reserve := range reservations {
+		total := atypes.ResourceUnits{}
 
-	for _, reservation := range reservations {
-		total := atypes.Unit{}
-
-		for _, resource := range reservation.Resources().GetResources() {
-			total.CPU += resource.Unit.CPU
-			total.Memory += resource.Unit.Memory
-			total.Storage += resource.Unit.Storage
+		for _, resource := range reserve.Resources().GetResources() {
+			// 🤔
+			if total, status.Error = total.Add(resource.Resources); status.Error != nil {
+				return status
+			}
 		}
 
-		if reservation.allocated {
+		if reserve.allocated {
 			status.Active = append(status.Active, total)
 		} else {
 			status.Pending = append(status.Pending, total)
@@ -342,18 +337,13 @@ func (is *inventoryService) getStatus(
 	}
 
 	for _, node := range inventory {
-		status.Available = append(status.Available, atypes.Unit{
-			CPU:     node.Available().CPU,
-			Memory:  node.Available().Memory,
-			Storage: node.Available().Storage,
-		})
+		status.Available = append(status.Available, node.Available())
 	}
 
 	return status
 }
 
 func reservationAllocateable(inventory []Node, reservations []*reservation, newReservation *reservation) bool {
-
 	// 1. for each unallocated reservation, subtract its resources
 	//    from inventory.
 	// 2. subtract resources for new reservation from inventory.
@@ -377,33 +367,29 @@ func reservationAllocateable(inventory []Node, reservations []*reservation, newR
 }
 
 func reservationAdjustInventory(prevInventory []Node, reservation *reservation) ([]Node, bool) {
-
 	// for each node in the inventory
 	//   subtract resource capacity from node capacity if the former will fit in the latter
 	//   remove resource capacity that fit in node capacity from requested resource capacity
 	// return remaining inventory, true iff all resources were able to fit
 
-	resources := make([]atypes.Resource, len(reservation.resources.GetResources()))
+	resources := make([]atypes.Resources, len(reservation.resources.GetResources()))
 	copy(resources, reservation.resources.GetResources())
 
 	inventory := make([]Node, 0, len(prevInventory))
 
 	for _, node := range prevInventory {
 		available := node.Available()
-
 		curResources := resources[:0]
 
 		for _, resource := range resources {
-
 			for ; resource.Count > 0; resource.Count-- {
-				if available.CPU < resource.Unit.CPU ||
-					available.Memory < resource.Unit.Memory ||
-					available.Storage < resource.Unit.Storage {
+				var err error
+				var remaining atypes.ResourceUnits
+				if remaining, err = available.Sub(resource.Resources); err != nil {
 					break
 				}
-				available.CPU -= resource.Unit.CPU
-				available.Memory -= resource.Unit.Memory
-				available.Storage -= resource.Unit.Storage
+
+				available = remaining
 			}
 
 			if resource.Count > 0 {
diff --git a/provider/cluster/kube/builder.go b/provider/cluster/kube/builder.go
index 3fa78022..77b74176 100644
--- a/provider/cluster/kube/builder.go
+++ b/provider/cluster/kube/builder.go
@@ -8,9 +8,6 @@ import (
 	"strings"
 
 	"github.com/lithammer/shortuuid"
-	"github.com/ovrclk/akash/manifest"
-	akashv1 "github.com/ovrclk/akash/pkg/apis/akash.network/v1"
-	mtypes "github.com/ovrclk/akash/x/market/types"
 	"github.com/tendermint/tendermint/libs/log"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -18,6 +15,10 @@ import (
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"github.com/ovrclk/akash/manifest"
+	akashv1 "github.com/ovrclk/akash/pkg/apis/akash.network/v1"
+	mtypes "github.com/ovrclk/akash/x/market/types"
 )
 
 const (
@@ -140,8 +141,6 @@ func (b *deploymentBuilder) update(obj *appsv1.Deployment) (*appsv1.Deployment,
 }
 
 func (b *deploymentBuilder) container() corev1.Container {
-	qcpu := resource.NewScaledQuantity(int64(b.service.Unit.CPU), resource.Milli)
-	qmem := resource.NewQuantity(int64(b.service.Unit.Memory), resource.DecimalSI)
 	falseValue := false
 
 	kcontainer := corev1.Container{
@@ -150,15 +149,9 @@ func (b *deploymentBuilder) container() corev1.Container {
 		Command: b.service.Command,
 		Args:    b.service.Args,
 		Resources: corev1.ResourceRequirements{
-			Limits: corev1.ResourceList{
-				corev1.ResourceCPU:    qcpu.DeepCopy(),
-				corev1.ResourceMemory: qmem.DeepCopy(),
-			},
+			Limits: make(corev1.ResourceList),
 			// TODO: this prevents over-subscription.  skip for now.
-			// Requests: corev1.ResourceList{
-			// 	corev1.ResourceCPU:    qcpu.DeepCopy(),
-			// 	corev1.ResourceMemory: qmem.DeepCopy(),
-			// },
+			// Requests: make(corev1.ResourceList),
 		},
 		SecurityContext: &corev1.SecurityContext{
 			RunAsNonRoot:             &falseValue,
@@ -167,6 +160,16 @@ func (b *deploymentBuilder) container() corev1.Container {
 		},
 	}
 
+	if cpu := b.service.Resources.CPU; cpu != nil {
+		kcontainer.Resources.Limits[corev1.ResourceCPU] = resource.NewScaledQuantity(int64(cpu.Units.Value()), resource.Milli).DeepCopy()
+	}
+
+	if mem := b.service.Resources.Memory; mem != nil {
+		kcontainer.Resources.Limits[corev1.ResourceMemory] = resource.NewQuantity(int64(mem.Quantity.Value()), resource.DecimalSI).DeepCopy()
+	}
+
+	// TODO: this prevents over-subscription.  skip for now.
+
 	for _, env := range b.service.Env {
 		parts := strings.Split(env, "=")
 		switch len(parts) {
diff --git a/provider/cluster/kube/client.go b/provider/cluster/kube/client.go
index e332babe..fcd9d4c3 100644
--- a/provider/cluster/kube/client.go
+++ b/provider/cluster/kube/client.go
@@ -289,20 +289,18 @@ func (c *client) ServiceStatus(ctx context.Context, lid mtypes.LeaseID, name str
 }
 
 func (c *client) Inventory(ctx context.Context) ([]cluster.Node, error) {
-
 	knodes, err := c.activeNodes(ctx)
 	if err != nil {
 		return nil, err
 	}
 
-	mnodes, err := c.metc.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
+	nodeMetrics, err := c.metc.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
 	if err != nil {
 		return nil, err
 	}
 
-	nodes := make([]cluster.Node, 0, len(mnodes.Items))
-	for _, mnode := range mnodes.Items {
-
+	nodes := make([]cluster.Node, 0, len(nodeMetrics.Items))
+	for _, mnode := range nodeMetrics.Items {
 		knode, ok := knodes[mnode.Name]
 		if !ok {
 			continue
@@ -326,22 +324,47 @@ func (c *client) Inventory(ctx context.Context) ([]cluster.Node, error) {
 			storage = 0
 		}
 
-		unit := types.Unit{
-			CPU:     uint32(cpu),
-			Memory:  uint64(memory),
-			Storage: uint64(storage),
+		resources := types.ResourceUnits{
+			CPU: &types.CPU{
+				Units: types.NewResourceValue(uint64(cpu)),
+				Attributes: []types.Attribute{
+					{
+						Key:   "arch",
+						Value: knode.Status.NodeInfo.Architecture,
+						// Value: types.NewAttributeValue(
+						// 	[]string{
+						// 		knode.Status.NodeInfo.Architecture,
+						// 	}),
+					},
+					// todo (#788) other node attributes ?
+				},
+			},
+			Memory: &types.Memory{
+				Quantity: types.NewResourceValue(uint64(memory)),
+				// todo (#788) memory attributes ?
+			},
+			Storage: &types.Storage{
+				Quantity: types.NewResourceValue(uint64(storage)),
+				// todo (#788) storage attributes like class and iops?
+			},
 		}
 
-		nodes = append(nodes, cluster.NewNode(knode.Name, unit))
+		nodes = append(nodes, cluster.NewNode(knode.Name, resources))
 	}
 
 	if os.Getenv("AKASH_PROVIDER_FAKE_CAPACITY") == "true" {
 		cfg := validation.Config()
 		return []cluster.Node{
-			cluster.NewNode("minikube", types.Unit{
-				CPU:     uint32(cfg.MaxUnitCPU * 100),
-				Memory:  uint64(cfg.MaxUnitMemory * 100),
-				Storage: uint64(cfg.MaxUnitStorage * 100),
+			cluster.NewNode("minikube", types.ResourceUnits{
+				CPU: &types.CPU{
+					Units: types.NewResourceValue(uint64(cfg.MaxUnitCPU * 100)),
+				},
+				Memory: &types.Memory{
+					Quantity: types.NewResourceValue(uint64(cfg.MaxUnitMemory * 100)),
+				},
+				Storage: &types.Storage{
+					Quantity: types.NewResourceValue(uint64(cfg.MaxUnitStorage * 100)),
+				},
 			}),
 		}, nil
 	}
diff --git a/provider/cluster/types.go b/provider/cluster/types.go
index a7c494a7..a7cf90c8 100644
--- a/provider/cluster/types.go
+++ b/provider/cluster/types.go
@@ -10,9 +10,10 @@ type Status struct {
 
 // InventoryStatus stores active, pending and available units
 type InventoryStatus struct {
-	Active    []atypes.Unit `json:"active"`
-	Pending   []atypes.Unit `json:"pending"`
-	Available []atypes.Unit `json:"available"`
+	Active    []atypes.ResourceUnits `json:"active"`
+	Pending   []atypes.ResourceUnits `json:"pending"`
+	Available []atypes.ResourceUnits `json:"available"`
+	Error     error                  `json:"error"`
 }
 
 // ServiceStatus stores the current status of service
diff --git a/sdl/coin.go b/sdl/coin.go
new file mode 100644
index 00000000..67f84283
--- /dev/null
+++ b/sdl/coin.go
@@ -0,0 +1,37 @@
+package sdl
+
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"gopkg.in/yaml.v3"
+)
+
+// v2Coin is an alias sdk.Coin to allow our custom UnmarshalYAML
+// for now it supports PoC when actual pricing is specified as two fields
+// aka amount and denom. we let UnmarshalYAML to deal with that and put result
+// into Value field.
+// discussion https://github.com/ovrclk/akash/issues/771
+type v2Coin struct {
+	Value sdk.Coin `yaml:"-"`
+}
+
+func (sdl *v2Coin) UnmarshalYAML(node *yaml.Node) error {
+	parsedCoin := struct {
+		Amount string `yaml:"amount"`
+		Denom  string `yaml:"denom"`
+	}{}
+
+	if err := node.Decode(&parsedCoin); err != nil {
+		return err
+	}
+
+	coin, err := sdk.ParseCoin(parsedCoin.Amount + parsedCoin.Denom)
+	if err != nil {
+		return err
+	}
+
+	*sdl = v2Coin{
+		Value: coin,
+	}
+
+	return nil
+}
diff --git a/sdl/cpu.go b/sdl/cpu.go
new file mode 100644
index 00000000..2b407c67
--- /dev/null
+++ b/sdl/cpu.go
@@ -0,0 +1,47 @@
+package sdl
+
+import (
+	"sort"
+
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/types"
+)
+
+type v2CPUAttributes types.Attributes
+
+type v2ResourceCPU struct {
+	Units      cpuQuantity     `yaml:"units"`
+	Attributes v2CPUAttributes `yaml:"attributes,omitempty"`
+}
+
+func (sdl *v2CPUAttributes) UnmarshalYAML(node *yaml.Node) error {
+	var attr v2CPUAttributes
+
+	for i := 0; i+1 < len(node.Content); i += 2 {
+		var value string
+		switch node.Content[i].Value {
+		case "arch":
+			if err := node.Content[i+1].Decode(&value); err != nil {
+				return err
+			}
+		default:
+			return errors.Errorf("unsupported cpu attribute \"%s\"", node.Content[i].Value)
+		}
+
+		attr = append(attr, types.Attribute{
+			Key:   node.Content[i].Value,
+			Value: value,
+		})
+	}
+
+	// keys are unique in attributes parsed from sdl so don't need to use sort.SliceStable
+	sort.Slice(attr, func(i, j int) bool {
+		return attr[i].Key < attr[j].Key
+	})
+
+	*sdl = attr
+
+	return nil
+}
diff --git a/sdl/expose.go b/sdl/expose.go
index 737d1620..6ea0b958 100644
--- a/sdl/expose.go
+++ b/sdl/expose.go
@@ -1,17 +1,27 @@
 package sdl
 
-import "net/url"
+import (
+	"net/url"
 
-func (p *v1Accept) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	"gopkg.in/yaml.v3"
+)
+
+type v2Accept struct {
+	Items []string `yaml:"items,omitempty"`
+}
+
+func (p *v2Accept) UnmarshalYAML(node *yaml.Node) error {
 	var accept []string
-	if err := unmarshal(&accept); err != nil {
+	if err := node.Decode(&accept); err != nil {
 		return err
 	}
+
 	for _, item := range accept {
 		if _, err := url.ParseRequestURI("http://" + item); err != nil {
 			return err
 		}
 	}
+
 	p.Items = accept
 	return nil
 }
diff --git a/sdl/memory.go b/sdl/memory.go
new file mode 100644
index 00000000..be54c4f5
--- /dev/null
+++ b/sdl/memory.go
@@ -0,0 +1,42 @@
+package sdl
+
+import (
+	"sort"
+
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/types"
+)
+
+type v2MemoryAttributes types.Attributes
+
+type v2ResourceMemory struct {
+	Quantity   byteQuantity       `yaml:"size"`
+	Attributes v2MemoryAttributes `yaml:"-"`
+}
+
+func (sdl *v2MemoryAttributes) UnmarshalYAML(node *yaml.Node) error {
+	var attr v2MemoryAttributes
+
+	var res map[string]string
+
+	if err := node.Decode(&res); err != nil {
+		return err
+	}
+
+	for k, v := range res {
+		attr = append(attr, types.Attribute{
+			Key:   k,
+			Value: v,
+		})
+	}
+
+	// keys are unique in attributes parsed from sdl so don't need to use sort.SliceStable
+	sort.Slice(attr, func(i, j int) bool {
+		return attr[i].Key < attr[j].Key
+	})
+
+	*sdl = attr
+
+	return nil
+}
diff --git a/sdl/placement.go b/sdl/placement.go
new file mode 100644
index 00000000..3b950d3c
--- /dev/null
+++ b/sdl/placement.go
@@ -0,0 +1,37 @@
+package sdl
+
+import (
+	"sort"
+
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/types"
+)
+
+type v2PlacementAttributes types.Attributes
+
+func (sdl *v2PlacementAttributes) UnmarshalYAML(node *yaml.Node) error {
+	var attr v2PlacementAttributes
+
+	var res map[string]string
+
+	if err := node.Decode(&res); err != nil {
+		return err
+	}
+
+	for k, v := range res {
+		attr = append(attr, types.Attribute{
+			Key:   k,
+			Value: v,
+		})
+	}
+
+	// keys are unique in attributes parsed from sdl so don't need to use sort.SliceStable
+	sort.Slice(attr, func(i, j int) bool {
+		return attr[i].Key < attr[j].Key
+	})
+
+	*sdl = attr
+
+	return nil
+}
diff --git a/sdl/pricing.go b/sdl/pricing.go
new file mode 100644
index 00000000..db2f1978
--- /dev/null
+++ b/sdl/pricing.go
@@ -0,0 +1,4 @@
+package sdl
+
+// todo should pricing values be in form of range?
+type v2PlacementPricing map[string]v2Coin
diff --git a/sdl/resources.go b/sdl/resources.go
new file mode 100644
index 00000000..eef73647
--- /dev/null
+++ b/sdl/resources.go
@@ -0,0 +1,39 @@
+package sdl
+
+import (
+	"github.com/ovrclk/akash/types"
+)
+
+type v2ComputeResources struct {
+	CPU     *v2ResourceCPU     `yaml:"cpu"`
+	Memory  *v2ResourceMemory  `yaml:"memory"`
+	Storage *v2ResourceStorage `yaml:"storage"`
+}
+
+func (sdl *v2ComputeResources) toResourceUnits() types.ResourceUnits {
+	if sdl == nil {
+		return types.ResourceUnits{}
+	}
+
+	var units types.ResourceUnits
+	if sdl.CPU != nil {
+		units.CPU = &types.CPU{
+			Units:      types.NewResourceValue(uint64(sdl.CPU.Units)),
+			Attributes: sdl.CPU.Attributes,
+		}
+	}
+	if sdl.Memory != nil {
+		units.Memory = &types.Memory{
+			Quantity:   types.NewResourceValue(uint64(sdl.Memory.Quantity)),
+			Attributes: sdl.Memory.Attributes,
+		}
+	}
+	if sdl.Storage != nil {
+		units.Storage = &types.Storage{
+			Quantity:   types.NewResourceValue(uint64(sdl.Storage.Quantity)),
+			Attributes: sdl.Storage.Attributes,
+		}
+	}
+
+	return units
+}
diff --git a/sdl/sdl.go b/sdl/sdl.go
index fea97739..5d32d04d 100644
--- a/sdl/sdl.go
+++ b/sdl/sdl.go
@@ -5,7 +5,9 @@ import (
 	"encoding/json"
 	"io/ioutil"
 
-	yaml "gopkg.in/yaml.v2"
+	"github.com/blang/semver"
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v3"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
@@ -14,13 +16,54 @@ import (
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
 )
 
+var (
+	errUninitializedConfig = errors.New("uninitialized config")
+)
+
 // SDL is the interface which wraps Validate, Deployment and Manifest methods
 type SDL interface {
-	Validate() error
 	DeploymentGroups() ([]*dtypes.GroupSpec, error)
 	Manifest() (manifest.Manifest, error)
 }
 
+var _ SDL = (*sdl)(nil)
+
+type sdl struct {
+	Version semver.Version `yaml:"-"`
+	data    SDL            `yaml:"-"`
+}
+
+func (s *sdl) UnmarshalYAML(node *yaml.Node) error {
+	var result sdl
+
+	for idx := range node.Content {
+		if node.Content[idx].Value == "version" {
+			var err error
+			if result.Version, err = semver.ParseTolerant(node.Content[idx+1].Value); err != nil {
+				return err
+			}
+
+			break
+		}
+	}
+
+	// nolint: gocritic
+	if result.Version.GE(semver.MustParse("2.0.0")) && result.Version.LT(semver.MustParse("3.0.0")) {
+		var decoded v2
+		if err := node.Decode(&decoded); err != nil {
+			return err
+		}
+
+		result.data = &decoded
+	} else {
+		return errors.Errorf("config: unsupported version")
+	}
+
+	*s = result
+
+	return nil
+}
+
 // ReadFile read from given path and returns SDL instance
 func ReadFile(path string) (SDL, error) {
 	buf, err := ioutil.ReadFile(path)
@@ -32,16 +75,11 @@ func ReadFile(path string) (SDL, error) {
 
 // Read reads buffer data and returns SDL instance
 func Read(buf []byte) (SDL, error) {
-	// TODO: handle versions; read 'version' field and switch
-	obj := &v1{}
+	obj := &sdl{}
 	if err := yaml.Unmarshal(buf, obj); err != nil {
 		return nil, err
 	}
 
-	if err := obj.Validate(); err != nil {
-		return nil, err
-	}
-
 	dgroups, err := obj.DeploymentGroups()
 	if err != nil {
 		return nil, err
@@ -82,7 +120,7 @@ func Version(s SDL) ([]byte, error) {
 }
 
 // ManifestVersion calculates the identifying deterministic hash for an SDL.
-// Sha256 returns 32 byte  sum of the SDL.
+// Sha256 returns 32 byte sum of the SDL.
 func ManifestVersion(manifest manifest.Manifest) ([]byte, error) {
 	m, err := json.Marshal(manifest)
 	if err != nil {
@@ -95,5 +133,22 @@ func ManifestVersion(manifest manifest.Manifest) ([]byte, error) {
 	}
 
 	sum := sha256.Sum256(sortedBytes)
-	return []byte(sum[:]), nil
+
+	return sum[:], nil
+}
+
+func (s *sdl) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
+	if s.data == nil {
+		return []*dtypes.GroupSpec{}, errUninitializedConfig
+	}
+
+	return s.data.DeploymentGroups()
+}
+
+func (s *sdl) Manifest() (manifest.Manifest, error) {
+	if s.data == nil {
+		return manifest.Manifest{}, errUninitializedConfig
+	}
+
+	return s.data.Manifest()
 }
diff --git a/sdl/storage.go b/sdl/storage.go
new file mode 100644
index 00000000..014282e8
--- /dev/null
+++ b/sdl/storage.go
@@ -0,0 +1,42 @@
+package sdl
+
+import (
+	"sort"
+
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/types"
+)
+
+type v2StorageAttributes types.Attributes
+
+type v2ResourceStorage struct {
+	Quantity   byteQuantity        `yaml:"size"`
+	Attributes v2StorageAttributes `yaml:"attributes,omitempty"`
+}
+
+func (sdl *v2StorageAttributes) UnmarshalYAML(node *yaml.Node) error {
+	var attr v2StorageAttributes
+
+	var res map[string]string
+
+	if err := node.Decode(&res); err != nil {
+		return err
+	}
+
+	for k, v := range res {
+		attr = append(attr, types.Attribute{
+			Key:   k,
+			Value: v,
+		})
+	}
+
+	// keys are unique in attributes parsed from sdl so don't need to use sort.SliceStable
+	sort.Slice(attr, func(i, j int) bool {
+		return attr[i].Key < attr[j].Key
+	})
+
+	*sdl = attr
+
+	return nil
+}
diff --git a/sdl/units.go b/sdl/units.go
index 50aee17d..99434377 100644
--- a/sdl/units.go
+++ b/sdl/units.go
@@ -5,6 +5,8 @@ import (
 	"strconv"
 	"strings"
 
+	"gopkg.in/yaml.v3"
+
 	"github.com/ovrclk/akash/types/unit"
 )
 
@@ -38,13 +40,8 @@ var unitSuffixes = []struct {
 // CPU shares.  One CPUQuantity = 1/1000 of a CPU
 type cpuQuantity uint32
 
-func (u *cpuQuantity) UnmarshalYAML(unmarshal func(interface{}) error) error {
-
-	var sval string
-	if err := unmarshal(&sval); err != nil {
-		return err
-	}
-
+func (u *cpuQuantity) UnmarshalYAML(node *yaml.Node) error {
+	sval := node.Value
 	if strings.HasSuffix(sval, "m") {
 		sval = strings.TrimSuffix(sval, "m")
 		val, err := strconv.ParseUint(sval, 10, 32)
@@ -74,12 +71,8 @@ func (u *cpuQuantity) UnmarshalYAML(unmarshal func(interface{}) error) error {
 // Memory,Storage size in bytes.
 type byteQuantity uint64
 
-func (u *byteQuantity) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	var sval string
-	if err := unmarshal(&sval); err != nil {
-		return err
-	}
-	val, err := parseWithSuffix(sval)
+func (u *byteQuantity) UnmarshalYAML(node *yaml.Node) error {
+	val, err := parseWithSuffix(node.Value)
 	if err != nil {
 		return err
 	}
diff --git a/sdl/v1.go b/sdl/v2.go
similarity index 55%
rename from sdl/v1.go
rename to sdl/v2.go
index 65a65cf3..d3f69624 100644
--- a/sdl/v1.go
+++ b/sdl/v2.go
@@ -3,94 +3,46 @@ package sdl
 import (
 	"sort"
 
-	"github.com/blang/semver"
 	"github.com/pkg/errors"
 
-	sdk "github.com/cosmos/cosmos-sdk/types"
-
 	"github.com/ovrclk/akash/manifest"
-	"github.com/ovrclk/akash/types"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
 )
 
-var (
-	allowedVersion = semver.MustParse("1.5.0")
-)
-
-var _ SDL = (*v1)(nil) // Assert interface implementation
-
-type v1 struct {
-	Version  string   `yaml:"version"`
-	Include  []string `yaml:",omitempty"`
-	Services map[string]v1Service
-	Profiles v1Profiles
-
-	// service-name -> { placement-profile -> { compute-profile, count } }
-	Deployments map[string]v1Deployment `yaml:"deployment"`
+type v2 struct {
+	Include     []string                `yaml:",omitempty"`
+	Services    map[string]v2Service    `yaml:"services,omitempty"`
+	Profiles    v2profiles              `yaml:"profiles,omitempty"`
+	Deployments map[string]v2Deployment `yaml:"deployment"`
 }
 
-type v1Service struct {
-	Image        string
-	Command      []string       `yaml:",omitempty"`
-	Args         []string       `yaml:",omitempty"`
-	Env          []string       `yaml:",omitempty"`
-	Expose       []v1Expose     `yaml:",omitempty"`
-	Dependencies []v1Dependency `yaml:",omitempty"`
+type v2ExposeTo struct {
+	Service string `yaml:"service,omitempty"`
+	Global  bool   `yaml:"global,omitempty"`
 }
 
-type v1Expose struct {
+type v2Expose struct {
 	Port   uint16
 	As     uint16
-	Proto  string       `yaml:",omitempty"`
-	To     []v1ExposeTo `yaml:",omitempty"`
-	Accept v1Accept
-}
-
-type v1Accept struct {
-	Items []string `yaml:",omitempty"`
-}
-
-type v1ExposeTo struct {
-	Service string `yaml:",omitempty"`
-	Global  bool   `yaml:",omitempty"`
-}
-
-type v1Dependency struct {
-	Service string
+	Proto  string       `yaml:"proto,omitempty"`
+	To     []v2ExposeTo `yaml:"to,omitempty"`
+	Accept v2Accept     `yaml:"accept"`
 }
 
-type v1Profiles struct {
-	Compute   map[string]v1ComputeProfile
-	Placement map[string]v1PlacementProfile
+type v2Dependency struct {
+	Service string `yaml:"service"`
 }
 
-type v1ComputeProfile struct {
-	CPU     cpuQuantity `yaml:"cpu"`
-	Memory  byteQuantity
-	Storage byteQuantity
-}
-
-type v1PlacementProfile struct {
-	Attributes map[string]string
-	Pricing    map[string]v1PricingProfile
-}
-
-// TODO: make coin parsing "just work".  wtf.
-type v1PricingProfile struct {
-	Denom  string
-	Amount string
-}
-
-func (pp v1PricingProfile) ToCoin() sdk.Coin {
-	amt, _ := sdk.NewIntFromString(pp.Amount)
-	coin := sdk.NewCoin(pp.Denom, amt)
-	return coin
+type v2Service struct {
+	Image        string
+	Command      []string       `yaml:",omitempty"`
+	Args         []string       `yaml:",omitempty"`
+	Env          []string       `yaml:",omitempty"`
+	Expose       []v2Expose     `yaml:",omitempty"`
+	Dependencies []v2Dependency `yaml:",omitempty"`
 }
 
-// placement-profile -> { compute-profile, count }
-type v1Deployment map[string]v1ServiceDeployment
-
-type v1ServiceDeployment struct {
+type v2ServiceDeployment struct {
 	// Compute profile name
 	Profile string
 
@@ -98,31 +50,31 @@ type v1ServiceDeployment struct {
 	Count uint32
 }
 
-func (sdl *v1) Validate() error {
-
-	if sdl.Version == "" {
-		return errors.Errorf("invalid version: '%v' required", allowedVersion)
-	}
+// placement-profile -> { compute-profile, count }
+type v2Deployment map[string]v2ServiceDeployment
 
-	vsn, err := semver.ParseTolerant(sdl.Version)
-	if err != nil {
-		return err
-	}
+type v2ProfileCompute struct {
+	// todo are compute resources mandatory ?
+	Resources *v2ComputeResources `yaml:"resources,omitempty"`
+}
 
-	if !allowedVersion.EQ(vsn) {
-		return errors.Errorf("invalid version: '%v' required", allowedVersion)
-	}
+type v2ProfilePlacement struct {
+	Attributes v2PlacementAttributes `yaml:"attributes"`
+	Pricing    v2PlacementPricing    `yaml:"pricing"`
+}
 
-	return nil
+type v2profiles struct {
+	Compute   map[string]v2ProfileCompute   `yaml:"compute"`
+	Placement map[string]v2ProfilePlacement `yaml:"placement"`
 }
 
-func (sdl *v1) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
+func (sdl *v2) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
 	groups := make(map[string]*dtypes.GroupSpec)
 
-	for _, svcName := range v1DeploymentSvcNames(sdl.Deployments) {
+	for _, svcName := range v2DeploymentSvcNames(sdl.Deployments) {
 		depl := sdl.Deployments[svcName]
 
-		for _, placementName := range v1DeploymentPlacementNames(depl) {
+		for _, placementName := range v2DeploymentPlacementNames(depl) {
 			svcdepl := depl[placementName]
 
 			compute, ok := sdl.Profiles.Compute[svcdepl.Profile]
@@ -147,30 +99,25 @@ func (sdl *v1) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
 					Name: placementName,
 				}
 
-				for k, v := range infra.Attributes {
-					group.Requirements = append(group.Requirements, sdk.Attribute{
-						Key:   k,
-						Value: v,
-					})
+				for _, v := range infra.Attributes {
+					group.Requirements = append(group.Requirements, v)
 				}
 
 				// keep ordering stable
 				sort.Slice(group.Requirements, func(i, j int) bool {
-					return string(group.Requirements[i].Key) < string(group.Requirements[j].Key)
+					return group.Requirements[i].Key < group.Requirements[j].Key
 				})
 
 				groups[placementName] = group
 			}
 
-			group.Resources = append(group.Resources, dtypes.Resource{
-				Unit: types.Unit{
-					CPU:     uint32(compute.CPU),
-					Memory:  uint64(compute.Memory),
-					Storage: uint64(compute.Storage),
-				},
-				Price: price.ToCoin(),
-				Count: svcdepl.Count,
-			})
+			resources := dtypes.Resource{
+				Resources: compute.Resources.toResourceUnits(),
+				Price:     price.Value,
+				Count:     svcdepl.Count,
+			}
+
+			group.Resources = append(group.Resources, resources)
 
 			// TODO: Make a parameter to configure the duration of orders being bid on
 			group.OrderBidDuration = dtypes.DefaultOrderBiddingDuration
@@ -192,14 +139,13 @@ func (sdl *v1) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
 	return result, nil
 }
 
-func (sdl *v1) Manifest() (manifest.Manifest, error) {
-
+func (sdl *v2) Manifest() (manifest.Manifest, error) {
 	groups := make(map[string]*manifest.Group)
 
-	for _, svcName := range v1DeploymentSvcNames(sdl.Deployments) {
+	for _, svcName := range v2DeploymentSvcNames(sdl.Deployments) {
 		depl := sdl.Deployments[svcName]
 
-		for _, placementName := range v1DeploymentPlacementNames(depl) {
+		for _, placementName := range v2DeploymentPlacementNames(depl) {
 			svcdepl := depl[placementName]
 
 			group := groups[placementName]
@@ -222,16 +168,12 @@ func (sdl *v1) Manifest() (manifest.Manifest, error) {
 			}
 
 			msvc := &manifest.Service{
-				Name:  svcName,
-				Image: svc.Image,
-				Args:  svc.Args,
-				Env:   svc.Env,
-				Unit: types.Unit{
-					CPU:     uint32(compute.CPU),
-					Memory:  uint64(compute.Memory),
-					Storage: uint64(compute.Storage),
-				},
-				Count: svcdepl.Count,
+				Name:      svcName,
+				Image:     svc.Image,
+				Args:      svc.Args,
+				Env:       svc.Env,
+				Resources: compute.Resources.toResourceUnits(),
+				Count:     svcdepl.Count,
 			}
 
 			for _, expose := range svc.Expose {
@@ -291,7 +233,7 @@ func (sdl *v1) Manifest() (manifest.Manifest, error) {
 }
 
 // stable ordering
-func v1DeploymentSvcNames(m map[string]v1Deployment) []string {
+func v2DeploymentSvcNames(m map[string]v2Deployment) []string {
 	names := make([]string, 0, len(m))
 	for name := range m {
 		names = append(names, name)
@@ -301,7 +243,7 @@ func v1DeploymentSvcNames(m map[string]v1Deployment) []string {
 }
 
 // stable ordering
-func v1DeploymentPlacementNames(m v1Deployment) []string {
+func v2DeploymentPlacementNames(m v2Deployment) []string {
 	names := make([]string, 0, len(m))
 	for name := range m {
 		names = append(names, name)
diff --git a/tools.go b/tools.go
index fd73e06a..d06e5177 100644
--- a/tools.go
+++ b/tools.go
@@ -4,9 +4,9 @@ package tools
 
 //nolint
 import (
+	_ "github.com/regen-network/cosmos-proto/protoc-gen-gocosmos"
 	_ "github.com/vektra/mockery"
 	_ "golang.org/x/tools/cmd/stringer"
 	_ "k8s.io/code-generator"
 	_ "sigs.k8s.io/kind"
-	_ "github.com/regen-network/cosmos-proto/protoc-gen-gocosmos"
 )
diff --git a/types/attribute.go b/types/attribute.go
new file mode 100644
index 00000000..d9224152
--- /dev/null
+++ b/types/attribute.go
@@ -0,0 +1,163 @@
+package types
+
+import (
+	"reflect"
+
+	"gopkg.in/yaml.v3"
+)
+
+/*
+Attributes purpose of using this type in favor of Cosmos's sdk.Attribute is
+ability to later extend it with operators to support querying on things like
+cpu/memory/storage attributes
+At this moment type though is same as sdk.Attributes but all akash libraries were
+turned to use a new one
+*/
+type Attributes []Attribute
+type AttributeValue string
+
+func NewStringAttribute(key, val string) Attribute {
+	return Attribute{
+		Key:   key,
+		Value: val,
+	}
+}
+
+func (m *Attribute) String() string {
+	res, _ := yaml.Marshal(m)
+	return string(res)
+}
+
+func (m *Attribute) Equal(rhs *Attribute) bool {
+	return reflect.DeepEqual(m, rhs)
+}
+
+func (m Attribute) SubsetOf(rhs Attribute) bool {
+	if m.Key == rhs.Key && m.Value == rhs.Value {
+		return true
+	}
+
+	return false
+}
+
+/*
+AttributesSubsetOf check if a is subset of that
+For example there are two yaml files being converted into these attributes
+example 1: a is subset of b
+---
+// a
+attributes:
+  region:
+    - us-east-1
+---
+b
+attributes:
+  region:
+    - us-east-1
+    - us-east-2
+
+example 2: a is not subset of b
+attributes:
+  region:
+    - us-east-1
+---
+b
+attributes:
+  region:
+    - us-east-2
+    - us-east-3
+
+example 3: a is subset of b
+attributes:
+  region:
+    - us-east-2
+    - us-east-3
+---
+b
+attributes:
+  region:
+    - us-east-2
+*/
+func AttributesSubsetOf(a, b Attributes) bool {
+loop:
+	for _, req := range a {
+		for _, attr := range b {
+			if req.SubsetOf(attr) {
+				continue loop
+			}
+		}
+		return false
+	}
+
+	return true
+}
+
+func (a Attributes) SubsetOf(that Attributes) bool {
+	return AttributesSubsetOf(a, that)
+}
+
+// type AttributeValue struct {
+// 	Val interface{} `json:"value" yaml:"value"`
+// }
+//
+// func NewAttributeValue(val interface{}) AttributeValue {
+// 	return AttributeValue{Val: val}
+// }
+//
+// func (a *AttributeValue) Reset() {
+// 	a.Val = nil
+// }
+//
+// func (a *AttributeValue) String() string {
+// 	res, _ := yaml.Marshal(&a.Val)
+// 	return string(res)
+// }
+//
+// func (a *AttributeValue) ProtoMessage() {}
+//
+// // Marshal implements the gogo proto custom type interface.
+// func (a AttributeValue) Marshal() ([]byte, error) {
+// 	return nil, nil
+// }
+//
+// // MarshalTo implements the gogo proto custom type interface.
+// func (a *AttributeValue) MarshalTo(data []byte) (n int, err error) {
+// 	return
+// }
+//
+// // Unmarshal implements the gogo proto custom type interface.
+// func (a *AttributeValue) Unmarshal(data []byte) error {
+// 	return nil
+// }
+//
+// // Size implements the gogo proto custom type interface.
+// func (a *AttributeValue) Size() int {
+// 	bz, _ := a.Marshal()
+// 	return len(bz)
+// }
+
+// func (m Attribute) SubsetOf(rhs Attribute) bool {
+// 	if m.Key == rhs.Key {
+// 		switch v := m.Value.Val.(type) {
+// 		case string:
+// 			v2 := rhs.Value.Val.(string)
+// 			if v == v2 {
+// 				return true
+// 			}
+// 		case []string:
+// 			v2 := rhs.Value.Val.([]string)
+// 			// fixme: turn into a func?
+// 			for _, mVal := range v {
+// 				for _, rhsVal := range v2 {
+// 					if mVal == rhsVal {
+// 						return true
+// 					}
+// 				}
+// 			}
+// 		default:
+// 			return false
+// 		}
+// 	}
+//
+// 	return false
+// }
diff --git a/types/attribute.pb.go b/types/attribute.pb.go
new file mode 100644
index 00000000..708968e5
--- /dev/null
+++ b/types/attribute.pb.go
@@ -0,0 +1,357 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: akash/attribute.proto
+
+package types
+
+import (
+	fmt "fmt"
+	_ "github.com/gogo/protobuf/gogoproto"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type Attribute struct {
+	Key   string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty" yaml:"key"`
+	Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`
+}
+
+func (m *Attribute) Reset()      { *m = Attribute{} }
+func (*Attribute) ProtoMessage() {}
+func (*Attribute) Descriptor() ([]byte, []int) {
+	return fileDescriptor_5ccd5f57ea879bdb, []int{0}
+}
+func (m *Attribute) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Attribute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Attribute.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Attribute) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Attribute.Merge(m, src)
+}
+func (m *Attribute) XXX_Size() int {
+	return m.Size()
+}
+func (m *Attribute) XXX_DiscardUnknown() {
+	xxx_messageInfo_Attribute.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Attribute proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*Attribute)(nil), "akash.Attribute")
+}
+
+func init() { proto.RegisterFile("akash/attribute.proto", fileDescriptor_5ccd5f57ea879bdb) }
+
+var fileDescriptor_5ccd5f57ea879bdb = []byte{
+	// 203 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4d, 0xcc, 0x4e, 0x2c,
+	0xce, 0xd0, 0x4f, 0x2c, 0x29, 0x29, 0xca, 0x4c, 0x2a, 0x2d, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f,
+	0xc9, 0x17, 0x62, 0x05, 0x0b, 0x4b, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x45, 0xf4, 0x41, 0x2c,
+	0x88, 0xa4, 0x52, 0x34, 0x17, 0xa7, 0x23, 0x4c, 0xbd, 0x90, 0x02, 0x17, 0x73, 0x76, 0x6a, 0xa5,
+	0x04, 0xa3, 0x02, 0xa3, 0x06, 0xa7, 0x13, 0xdf, 0xa7, 0x7b, 0xf2, 0x5c, 0x95, 0x89, 0xb9, 0x39,
+	0x56, 0x4a, 0xd9, 0xa9, 0x95, 0x4a, 0x41, 0x20, 0x29, 0x21, 0x35, 0x2e, 0xd6, 0xb2, 0xc4, 0x9c,
+	0xd2, 0x54, 0x09, 0x26, 0xb0, 0x1a, 0x81, 0x4f, 0xf7, 0xe4, 0x79, 0x20, 0x6a, 0xc0, 0xc2, 0x4a,
+	0x41, 0x10, 0x69, 0x2b, 0x96, 0x8e, 0x05, 0xf2, 0x0c, 0x4e, 0xf6, 0x37, 0x1e, 0xca, 0x31, 0x34,
+	0x3c, 0x92, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18,
+	0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xd9, 0xf4,
+	0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xfc, 0xb2, 0xa2, 0xe4, 0x9c, 0x6c,
+	0x7d, 0x88, 0x27, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x8e, 0x34, 0x06, 0x04, 0x00,
+	0x00, 0xff, 0xff, 0xb3, 0xdc, 0x7a, 0xfa, 0xda, 0x00, 0x00, 0x00,
+}
+
+func (m *Attribute) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Attribute) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Attribute) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Value) > 0 {
+		i -= len(m.Value)
+		copy(dAtA[i:], m.Value)
+		i = encodeVarintAttribute(dAtA, i, uint64(len(m.Value)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Key) > 0 {
+		i -= len(m.Key)
+		copy(dAtA[i:], m.Key)
+		i = encodeVarintAttribute(dAtA, i, uint64(len(m.Key)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintAttribute(dAtA []byte, offset int, v uint64) int {
+	offset -= sovAttribute(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *Attribute) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Key)
+	if l > 0 {
+		n += 1 + l + sovAttribute(uint64(l))
+	}
+	l = len(m.Value)
+	if l > 0 {
+		n += 1 + l + sovAttribute(uint64(l))
+	}
+	return n
+}
+
+func sovAttribute(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozAttribute(x uint64) (n int) {
+	return sovAttribute(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *Attribute) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAttribute
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Attribute: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Attribute: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAttribute
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Key = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAttribute
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAttribute(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthAttribute
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipAttribute(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowAttribute
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowAttribute
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowAttribute
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthAttribute
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupAttribute
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthAttribute
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthAttribute        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowAttribute          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupAttribute = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/types/resource.go b/types/resource.go
index adee2e06..611e547a 100644
--- a/types/resource.go
+++ b/types/resource.go
@@ -1,20 +1,264 @@
 package types
 
+import (
+	"reflect"
+)
+
+type UnitType int
+
+type Unit interface {
+	String() string
+	equals(Unit) bool
+	add(Unit) error
+	sub(Unit) error
+	le(Unit) bool
+}
+
+type ResUnit interface {
+	Equals(ResUnit) bool
+	Add(unit ResUnit) bool
+}
+
+// Resources stores Unit details and Count value
+type Resources struct {
+	Resources ResourceUnits `json:"resources"`
+	Count     uint32        `json:"count"`
+}
+
 // ResourceGroup is the interface that wraps GetName and GetResources methods
 type ResourceGroup interface {
 	GetName() string
-	GetResources() []Resource
+	GetResources() []Resources
+}
+
+var _ Unit = (*CPU)(nil)
+var _ Unit = (*Memory)(nil)
+var _ Unit = (*Storage)(nil)
+
+// AddUnit it rather searches for existing entry of the same type and sums values
+// if type not found it appends
+func (m ResourceUnits) Add(rhs ResourceUnits) (ResourceUnits, error) {
+	res := m
+
+	if res.CPU != nil {
+		if err := res.CPU.add(rhs.CPU); err != nil {
+			return ResourceUnits{}, err
+		}
+	} else {
+		res.CPU = rhs.CPU
+	}
+
+	if res.Memory != nil {
+		if err := res.Memory.add(rhs.Memory); err != nil {
+			return ResourceUnits{}, err
+		}
+	} else {
+		res.Memory = rhs.Memory
+	}
+
+	if res.Storage != nil {
+		if err := res.Storage.add(rhs.Storage); err != nil {
+			return ResourceUnits{}, err
+		}
+	} else {
+		res.Storage = rhs.Storage
+	}
+
+	return res, nil
+}
+
+// Sub tbd
+func (m ResourceUnits) Sub(rhs ResourceUnits) (ResourceUnits, error) {
+	if (m.CPU == nil && rhs.CPU != nil) ||
+		(m.Memory == nil && rhs.Memory != nil) ||
+		(m.Storage == nil && rhs.Storage != nil) {
+		return ResourceUnits{}, errCannotSub
+	}
+
+	res := m
+
+	if res.CPU != nil {
+		if err := res.CPU.sub(rhs.CPU); err != nil {
+			return ResourceUnits{}, err
+		}
+	}
+	if res.Memory != nil {
+		if err := res.Memory.sub(rhs.Memory); err != nil {
+			return ResourceUnits{}, err
+		}
+	}
+
+	if res.Storage != nil {
+		if err := res.Storage.sub(rhs.Storage); err != nil {
+			return ResourceUnits{}, err
+		}
+	}
+
+	return res, nil
+}
+
+func (m ResourceUnits) Equals(rhs ResourceUnits) bool {
+	return reflect.DeepEqual(m, rhs)
+}
+
+func (m *CPU) equals(other Unit) bool {
+	rhs, valid := other.(*CPU)
+	if !valid {
+		return false
+	}
+
+	if !m.Units.equals(rhs.Units) || len(m.Attributes) != len(rhs.Attributes) {
+		return false
+	}
+
+	return reflect.DeepEqual(m.Attributes, rhs.Attributes)
+}
+
+func (m *CPU) le(other Unit) bool {
+	rhs, valid := other.(*CPU)
+	if !valid {
+		return false
+	}
+
+	return m.Units.le(rhs.Units)
+}
+
+func (m *CPU) add(other Unit) error {
+	rhs, valid := other.(*CPU)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Units.add(rhs.Units)
+	if err != nil {
+		return err
+	}
+
+	m.Units = res
+
+	return nil
 }
 
-// Resource stores Unit details and Count value
-type Resource struct {
-	Unit  Unit
-	Count uint32
+func (m *CPU) sub(other Unit) error {
+	rhs, valid := other.(*CPU)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Units.sub(rhs.Units)
+	if err != nil {
+		return err
+	}
+
+	m.Units = res
+
+	return nil
+}
+
+func (m *Memory) equals(other Unit) bool {
+	rhs, valid := other.(*Memory)
+	if !valid {
+		return false
+	}
+
+	if !m.Quantity.equals(rhs.Quantity) || len(m.Attributes) != len(rhs.Attributes) {
+		return false
+	}
+
+	return reflect.DeepEqual(m.Attributes, rhs.Attributes)
+}
+
+func (m *Memory) le(other Unit) bool {
+	rhs, valid := other.(*Memory)
+	if !valid {
+		return false
+	}
+
+	return m.Quantity.le(rhs.Quantity)
 }
 
-// Equals compare given unit with receiver unit
-func (u Unit) Equals(other Unit) bool {
-	return u.CPU == other.CPU &&
-		u.Memory == other.Memory &&
-		u.Storage == other.Storage
+func (m *Memory) add(other Unit) error {
+	rhs, valid := other.(*Memory)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Quantity.add(rhs.Quantity)
+	if err != nil {
+		return err
+	}
+
+	m.Quantity = res
+
+	return nil
+}
+
+func (m *Memory) sub(other Unit) error {
+	rhs, valid := other.(*Memory)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Quantity.sub(rhs.Quantity)
+	if err != nil {
+		return err
+	}
+
+	m.Quantity = res
+
+	return nil
+}
+
+func (m *Storage) equals(other Unit) bool {
+	rhs, valid := other.(*Storage)
+	if !valid {
+		return false
+	}
+
+	if !m.Quantity.equals(rhs.Quantity) || len(m.Attributes) != len(rhs.Attributes) {
+		return false
+	}
+
+	return reflect.DeepEqual(m.Attributes, rhs.Attributes)
+}
+
+func (m *Storage) le(other Unit) bool {
+	rhs, valid := other.(*Storage)
+	if !valid {
+		return false
+	}
+
+	return m.Quantity.le(rhs.Quantity)
+}
+
+func (m *Storage) add(other Unit) error {
+	rhs, valid := other.(*Storage)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Quantity.add(rhs.Quantity)
+	if err != nil {
+		return err
+	}
+
+	m.Quantity = res
+
+	return nil
+}
+
+func (m *Storage) sub(other Unit) error {
+	rhs, valid := other.(*Storage)
+	if !valid {
+		return nil
+	}
+
+	res, err := m.Quantity.sub(rhs.Quantity)
+	if err != nil {
+		return err
+	}
+
+	m.Quantity = res
+
+	return nil
 }
diff --git a/types/resource.pb.go b/types/resource.pb.go
index 49435be5..13d554cf 100644
--- a/types/resource.pb.go
+++ b/types/resource.pb.go
@@ -23,25 +23,23 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
-// Unit stores cpu, memory and storage metrics
-type Unit struct {
-	CPU     uint32 `protobuf:"varint,1,opt,name=cpu,proto3" json:"cpu" yaml:"cpu"`
-	Memory  uint64 `protobuf:"varint,2,opt,name=memory,proto3" json:"memory" yaml:"memory"`
-	Storage uint64 `protobuf:"varint,3,opt,name=storage,proto3" json:"storage" yaml:"storage"`
+type CPU struct {
+	Units      ResourceValue `protobuf:"bytes,1,opt,name=units,proto3" json:"units"`
+	Attributes []Attribute   `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" yaml:"cpu,omitempty"`
 }
 
-func (m *Unit) Reset()         { *m = Unit{} }
-func (m *Unit) String() string { return proto.CompactTextString(m) }
-func (*Unit) ProtoMessage()    {}
-func (*Unit) Descriptor() ([]byte, []int) {
+func (m *CPU) Reset()         { *m = CPU{} }
+func (m *CPU) String() string { return proto.CompactTextString(m) }
+func (*CPU) ProtoMessage()    {}
+func (*CPU) Descriptor() ([]byte, []int) {
 	return fileDescriptor_1e6470e3190728b6, []int{0}
 }
-func (m *Unit) XXX_Unmarshal(b []byte) error {
+func (m *CPU) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
 }
-func (m *Unit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+func (m *CPU) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 	if deterministic {
-		return xxx_messageInfo_Unit.Marshal(b, m, deterministic)
+		return xxx_messageInfo_CPU.Marshal(b, m, deterministic)
 	} else {
 		b = b[:cap(b)]
 		n, err := m.MarshalToSizedBuffer(b)
@@ -51,73 +49,279 @@ func (m *Unit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 		return b[:n], nil
 	}
 }
-func (m *Unit) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Unit.Merge(m, src)
+func (m *CPU) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CPU.Merge(m, src)
 }
-func (m *Unit) XXX_Size() int {
+func (m *CPU) XXX_Size() int {
 	return m.Size()
 }
-func (m *Unit) XXX_DiscardUnknown() {
-	xxx_messageInfo_Unit.DiscardUnknown(m)
+func (m *CPU) XXX_DiscardUnknown() {
+	xxx_messageInfo_CPU.DiscardUnknown(m)
 }
 
-var xxx_messageInfo_Unit proto.InternalMessageInfo
+var xxx_messageInfo_CPU proto.InternalMessageInfo
 
-func (m *Unit) GetCPU() uint32 {
+func (m *CPU) GetUnits() ResourceValue {
+	if m != nil {
+		return m.Units
+	}
+	return ResourceValue{}
+}
+
+func (m *CPU) GetAttributes() []Attribute {
+	if m != nil {
+		return m.Attributes
+	}
+	return nil
+}
+
+type Memory struct {
+	Quantity   ResourceValue `protobuf:"bytes,1,opt,name=quantity,proto3" json:"size" yaml:"size"`
+	Attributes []Attribute   `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" yaml:"cpu,omitempty"`
+}
+
+func (m *Memory) Reset()         { *m = Memory{} }
+func (m *Memory) String() string { return proto.CompactTextString(m) }
+func (*Memory) ProtoMessage()    {}
+func (*Memory) Descriptor() ([]byte, []int) {
+	return fileDescriptor_1e6470e3190728b6, []int{1}
+}
+func (m *Memory) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Memory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Memory.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Memory) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Memory.Merge(m, src)
+}
+func (m *Memory) XXX_Size() int {
+	return m.Size()
+}
+func (m *Memory) XXX_DiscardUnknown() {
+	xxx_messageInfo_Memory.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Memory proto.InternalMessageInfo
+
+func (m *Memory) GetQuantity() ResourceValue {
+	if m != nil {
+		return m.Quantity
+	}
+	return ResourceValue{}
+}
+
+func (m *Memory) GetAttributes() []Attribute {
+	if m != nil {
+		return m.Attributes
+	}
+	return nil
+}
+
+type Storage struct {
+	Quantity   ResourceValue `protobuf:"bytes,1,opt,name=quantity,proto3" json:"size" yaml:"size"`
+	Attributes []Attribute   `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" yaml:"cpu,omitempty"`
+}
+
+func (m *Storage) Reset()         { *m = Storage{} }
+func (m *Storage) String() string { return proto.CompactTextString(m) }
+func (*Storage) ProtoMessage()    {}
+func (*Storage) Descriptor() ([]byte, []int) {
+	return fileDescriptor_1e6470e3190728b6, []int{2}
+}
+func (m *Storage) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Storage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Storage.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Storage) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Storage.Merge(m, src)
+}
+func (m *Storage) XXX_Size() int {
+	return m.Size()
+}
+func (m *Storage) XXX_DiscardUnknown() {
+	xxx_messageInfo_Storage.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Storage proto.InternalMessageInfo
+
+func (m *Storage) GetQuantity() ResourceValue {
+	if m != nil {
+		return m.Quantity
+	}
+	return ResourceValue{}
+}
+
+func (m *Storage) GetAttributes() []Attribute {
+	if m != nil {
+		return m.Attributes
+	}
+	return nil
+}
+
+// ResourceUnits describes all available resources types for deployment/node etc
+// if field is nil resource is not present in the given data-structure
+type ResourceUnits struct {
+	CPU     *CPU     `protobuf:"bytes,1,opt,name=CPU,proto3" json:"cpu,omitempty" yaml:"cpu,omitempty"`
+	Memory  *Memory  `protobuf:"bytes,2,opt,name=memory,proto3" json:"memory,omitempty" yaml:"memory,omitempty"`
+	Storage *Storage `protobuf:"bytes,3,opt,name=storage,proto3" json:"storage,omitempty" yaml:"storage,omitempty"`
+}
+
+func (m *ResourceUnits) Reset()         { *m = ResourceUnits{} }
+func (m *ResourceUnits) String() string { return proto.CompactTextString(m) }
+func (*ResourceUnits) ProtoMessage()    {}
+func (*ResourceUnits) Descriptor() ([]byte, []int) {
+	return fileDescriptor_1e6470e3190728b6, []int{3}
+}
+func (m *ResourceUnits) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ResourceUnits) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ResourceUnits.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ResourceUnits) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ResourceUnits.Merge(m, src)
+}
+func (m *ResourceUnits) XXX_Size() int {
+	return m.Size()
+}
+func (m *ResourceUnits) XXX_DiscardUnknown() {
+	xxx_messageInfo_ResourceUnits.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ResourceUnits proto.InternalMessageInfo
+
+func (m *ResourceUnits) GetCPU() *CPU {
 	if m != nil {
 		return m.CPU
 	}
-	return 0
+	return nil
 }
 
-func (m *Unit) GetMemory() uint64 {
+func (m *ResourceUnits) GetMemory() *Memory {
 	if m != nil {
 		return m.Memory
 	}
-	return 0
+	return nil
 }
 
-func (m *Unit) GetStorage() uint64 {
+func (m *ResourceUnits) GetStorage() *Storage {
 	if m != nil {
 		return m.Storage
 	}
-	return 0
+	return nil
 }
 
 func init() {
-	proto.RegisterType((*Unit)(nil), "akash.Unit")
+	proto.RegisterType((*CPU)(nil), "akash.CPU")
+	proto.RegisterType((*Memory)(nil), "akash.Memory")
+	proto.RegisterType((*Storage)(nil), "akash.Storage")
+	proto.RegisterType((*ResourceUnits)(nil), "akash.ResourceUnits")
 }
 
 func init() { proto.RegisterFile("akash/resource.proto", fileDescriptor_1e6470e3190728b6) }
 
 var fileDescriptor_1e6470e3190728b6 = []byte{
-	// 251 bytes of a gzipped FileDescriptorProto
+	// 433 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0xcc, 0x4e, 0x2c,
 	0xce, 0xd0, 0x2f, 0x4a, 0x2d, 0xce, 0x2f, 0x2d, 0x4a, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9,
 	0x17, 0x62, 0x05, 0x8b, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x45, 0xf4, 0x41, 0x2c, 0x88,
-	0xa4, 0xd2, 0x2a, 0x46, 0x2e, 0x96, 0xd0, 0xbc, 0xcc, 0x12, 0x21, 0x3d, 0x2e, 0xe6, 0xe4, 0x82,
-	0x52, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x5e, 0x27, 0x99, 0x47, 0xf7, 0xe4, 0x99, 0x9d, 0x03, 0x42,
-	0x5f, 0xdd, 0x93, 0x07, 0x89, 0x7e, 0xba, 0x27, 0xcf, 0x55, 0x99, 0x98, 0x9b, 0x63, 0xa5, 0x94,
-	0x5c, 0x50, 0xaa, 0x14, 0x04, 0x12, 0x12, 0x32, 0xe6, 0x62, 0xcb, 0x4d, 0xcd, 0xcd, 0x2f, 0xaa,
-	0x94, 0x60, 0x52, 0x60, 0xd4, 0x60, 0x71, 0x92, 0x7e, 0x75, 0x4f, 0x1e, 0x2a, 0xf2, 0xe9, 0x9e,
-	0x3c, 0x2f, 0x44, 0x39, 0x84, 0xaf, 0x14, 0x04, 0x95, 0x10, 0x32, 0xe7, 0x62, 0x2f, 0x2e, 0xc9,
-	0x2f, 0x4a, 0x4c, 0x4f, 0x95, 0x60, 0x06, 0xeb, 0x92, 0x7d, 0x75, 0x4f, 0x1e, 0x26, 0xf4, 0xe9,
-	0x9e, 0x3c, 0x1f, 0x44, 0x1b, 0x54, 0x40, 0x29, 0x08, 0x26, 0x65, 0xc5, 0xf2, 0x62, 0x81, 0x3c,
-	0xa3, 0x93, 0xf9, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38,
-	0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0xc9, 0xa6, 0x67,
-	0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xe7, 0x97, 0x15, 0x25, 0xe7, 0x64, 0xeb,
-	0x43, 0xc2, 0xa2, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0xec, 0x59, 0x63, 0x40, 0x00, 0x00,
-	0x00, 0xff, 0xff, 0x8e, 0xe7, 0xbb, 0x58, 0x21, 0x01, 0x00, 0x00,
-}
-
-func (this *Unit) Equal(that interface{}) bool {
+	0xa4, 0x94, 0x28, 0x44, 0x4b, 0x62, 0x49, 0x49, 0x51, 0x66, 0x52, 0x69, 0x09, 0x54, 0x8f, 0x94,
+	0x24, 0xaa, 0x49, 0x65, 0x89, 0x39, 0xa5, 0x50, 0x29, 0xa5, 0xa5, 0x8c, 0x5c, 0xcc, 0xce, 0x01,
+	0xa1, 0x42, 0x06, 0x5c, 0xac, 0xa5, 0x79, 0x99, 0x25, 0xc5, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc,
+	0x46, 0x22, 0x7a, 0x60, 0x2d, 0x7a, 0x41, 0x50, 0x2d, 0x61, 0x20, 0x2d, 0x4e, 0x2c, 0x27, 0xee,
+	0xc9, 0x33, 0x04, 0x41, 0x14, 0x0a, 0xa5, 0x71, 0x71, 0xc1, 0xed, 0x29, 0x96, 0x60, 0x52, 0x60,
+	0xd6, 0xe0, 0x36, 0x12, 0x80, 0x6a, 0x73, 0x84, 0x49, 0x38, 0x99, 0x80, 0xb4, 0xbc, 0xba, 0x27,
+	0x2f, 0x82, 0x50, 0xab, 0x93, 0x9f, 0x9b, 0x59, 0x92, 0x9a, 0x5b, 0x50, 0x52, 0xf9, 0xe9, 0x9e,
+	0xbc, 0x48, 0x65, 0x62, 0x6e, 0x8e, 0x95, 0x52, 0x72, 0x41, 0x29, 0x42, 0x58, 0x29, 0x08, 0xc9,
+	0x64, 0x2b, 0x96, 0x17, 0x0b, 0xe4, 0x19, 0x95, 0x8e, 0x32, 0x72, 0xb1, 0xf9, 0xa6, 0xe6, 0xe6,
+	0x17, 0x55, 0x0a, 0xf9, 0x72, 0x71, 0x14, 0x96, 0x26, 0xe6, 0x95, 0x64, 0x96, 0x54, 0xe2, 0x75,
+	0xad, 0x34, 0xd4, 0x6a, 0x96, 0xe2, 0xcc, 0xaa, 0xd4, 0x4f, 0xf7, 0xe4, 0xb9, 0x21, 0x56, 0x81,
+	0x78, 0x4a, 0x41, 0x70, 0x23, 0xe8, 0xec, 0x8f, 0x63, 0x8c, 0x5c, 0xec, 0xc1, 0x25, 0xf9, 0x45,
+	0x89, 0xe9, 0xa9, 0x43, 0xdb, 0x23, 0xf3, 0x99, 0xb8, 0x78, 0x61, 0xce, 0x0c, 0x05, 0x27, 0x08,
+	0x6f, 0x70, 0x4a, 0x82, 0xfa, 0x84, 0x0b, 0x6a, 0xb1, 0x73, 0x40, 0xa8, 0x93, 0xee, 0x89, 0x7b,
+	0xf2, 0x8c, 0xaf, 0xee, 0xc9, 0xf3, 0xa2, 0x18, 0x8a, 0xd3, 0x2e, 0x70, 0x7a, 0x8c, 0xe2, 0x62,
+	0xcb, 0x05, 0x47, 0xb7, 0x04, 0x13, 0xd8, 0x3c, 0x5e, 0xa8, 0x79, 0x90, 0x34, 0xe0, 0x64, 0x0c,
+	0x35, 0x52, 0x00, 0xa2, 0x08, 0xc5, 0x54, 0x71, 0x88, 0xa9, 0xe8, 0x32, 0x4a, 0x41, 0x50, 0x13,
+	0x85, 0xe2, 0xb9, 0xd8, 0x8b, 0x21, 0x51, 0x20, 0xc1, 0x0c, 0x36, 0x9c, 0x0f, 0x6a, 0x38, 0x34,
+	0x62, 0x9c, 0x4c, 0xa1, 0xa6, 0x0b, 0x42, 0x95, 0xa1, 0x18, 0x2f, 0x01, 0x0d, 0x7d, 0x74, 0x29,
+	0xa5, 0x20, 0x98, 0xa9, 0x90, 0x10, 0x72, 0x32, 0x3f, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39,
+	0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63,
+	0x39, 0x86, 0x28, 0xd9, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xfc,
+	0xb2, 0xa2, 0xe4, 0x9c, 0x6c, 0x7d, 0x48, 0x0e, 0x2d, 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03,
+	0x67, 0x4d, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x76, 0x10, 0xe1, 0x01, 0x04, 0x00,
+	0x00,
+}
+
+func (this *CPU) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*CPU)
+	if !ok {
+		that2, ok := that.(CPU)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if !this.Units.Equal(&that1.Units) {
+		return false
+	}
+	if len(this.Attributes) != len(that1.Attributes) {
+		return false
+	}
+	for i := range this.Attributes {
+		if !this.Attributes[i].Equal(&that1.Attributes[i]) {
+			return false
+		}
+	}
+	return true
+}
+func (this *Memory) Equal(that interface{}) bool {
 	if that == nil {
 		return this == nil
 	}
 
-	that1, ok := that.(*Unit)
+	that1, ok := that.(*Memory)
 	if !ok {
-		that2, ok := that.(Unit)
+		that2, ok := that.(Memory)
 		if ok {
 			that1 = &that2
 		} else {
@@ -129,91 +333,731 @@ func (this *Unit) Equal(that interface{}) bool {
 	} else if this == nil {
 		return false
 	}
-	if this.CPU != that1.CPU {
+	if !this.Quantity.Equal(&that1.Quantity) {
+		return false
+	}
+	if len(this.Attributes) != len(that1.Attributes) {
+		return false
+	}
+	for i := range this.Attributes {
+		if !this.Attributes[i].Equal(&that1.Attributes[i]) {
+			return false
+		}
+	}
+	return true
+}
+func (this *Storage) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*Storage)
+	if !ok {
+		that2, ok := that.(Storage)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
 		return false
 	}
-	if this.Memory != that1.Memory {
+	if !this.Quantity.Equal(&that1.Quantity) {
 		return false
 	}
-	if this.Storage != that1.Storage {
+	if len(this.Attributes) != len(that1.Attributes) {
 		return false
 	}
+	for i := range this.Attributes {
+		if !this.Attributes[i].Equal(&that1.Attributes[i]) {
+			return false
+		}
+	}
 	return true
 }
-func (m *Unit) Marshal() (dAtA []byte, err error) {
+func (this *ResourceUnits) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*ResourceUnits)
+	if !ok {
+		that2, ok := that.(ResourceUnits)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if !this.CPU.Equal(that1.CPU) {
+		return false
+	}
+	if !this.Memory.Equal(that1.Memory) {
+		return false
+	}
+	if !this.Storage.Equal(that1.Storage) {
+		return false
+	}
+	return true
+}
+func (m *CPU) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
 	n, err := m.MarshalToSizedBuffer(dAtA[:size])
 	if err != nil {
 		return nil, err
 	}
-	return dAtA[:n], nil
-}
-
-func (m *Unit) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
+	return dAtA[:n], nil
+}
+
+func (m *CPU) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *CPU) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Attributes) > 0 {
+		for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintResource(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	{
+		size, err := m.Units.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintResource(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *Memory) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Memory) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Memory) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Attributes) > 0 {
+		for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintResource(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	{
+		size, err := m.Quantity.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintResource(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *Storage) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Storage) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Storage) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Attributes) > 0 {
+		for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintResource(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	{
+		size, err := m.Quantity.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintResource(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *ResourceUnits) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ResourceUnits) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ResourceUnits) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Storage != nil {
+		{
+			size, err := m.Storage.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResource(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x1a
+	}
+	if m.Memory != nil {
+		{
+			size, err := m.Memory.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResource(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.CPU != nil {
+		{
+			size, err := m.CPU.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResource(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintResource(dAtA []byte, offset int, v uint64) int {
+	offset -= sovResource(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *CPU) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Units.Size()
+	n += 1 + l + sovResource(uint64(l))
+	if len(m.Attributes) > 0 {
+		for _, e := range m.Attributes {
+			l = e.Size()
+			n += 1 + l + sovResource(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *Memory) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Quantity.Size()
+	n += 1 + l + sovResource(uint64(l))
+	if len(m.Attributes) > 0 {
+		for _, e := range m.Attributes {
+			l = e.Size()
+			n += 1 + l + sovResource(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *Storage) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Quantity.Size()
+	n += 1 + l + sovResource(uint64(l))
+	if len(m.Attributes) > 0 {
+		for _, e := range m.Attributes {
+			l = e.Size()
+			n += 1 + l + sovResource(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *ResourceUnits) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.CPU != nil {
+		l = m.CPU.Size()
+		n += 1 + l + sovResource(uint64(l))
+	}
+	if m.Memory != nil {
+		l = m.Memory.Size()
+		n += 1 + l + sovResource(uint64(l))
+	}
+	if m.Storage != nil {
+		l = m.Storage.Size()
+		n += 1 + l + sovResource(uint64(l))
+	}
+	return n
+}
+
+func sovResource(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozResource(x uint64) (n int) {
+	return sovResource(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *CPU) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowResource
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: CPU: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: CPU: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Units", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Units.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Attributes = append(m.Attributes, Attribute{})
+			if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipResource(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (m *Unit) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	if m.Storage != 0 {
-		i = encodeVarintResource(dAtA, i, uint64(m.Storage))
-		i--
-		dAtA[i] = 0x18
-	}
-	if m.Memory != 0 {
-		i = encodeVarintResource(dAtA, i, uint64(m.Memory))
-		i--
-		dAtA[i] = 0x10
-	}
-	if m.CPU != 0 {
-		i = encodeVarintResource(dAtA, i, uint64(m.CPU))
-		i--
-		dAtA[i] = 0x8
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return len(dAtA) - i, nil
+	return nil
 }
+func (m *Memory) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowResource
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Memory: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Memory: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Quantity", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Quantity.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Attributes = append(m.Attributes, Attribute{})
+			if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipResource(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func encodeVarintResource(dAtA []byte, offset int, v uint64) int {
-	offset -= sovResource(v)
-	base := offset
-	for v >= 1<<7 {
-		dAtA[offset] = uint8(v&0x7f | 0x80)
-		v >>= 7
-		offset++
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	dAtA[offset] = uint8(v)
-	return base
+	return nil
 }
-func (m *Unit) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if m.CPU != 0 {
-		n += 1 + sovResource(uint64(m.CPU))
-	}
-	if m.Memory != 0 {
-		n += 1 + sovResource(uint64(m.Memory))
-	}
-	if m.Storage != 0 {
-		n += 1 + sovResource(uint64(m.Storage))
+func (m *Storage) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowResource
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Storage: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Storage: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Quantity", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Quantity.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResource
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Attributes = append(m.Attributes, Attribute{})
+			if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipResource(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthResource
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
 	}
-	return n
-}
 
-func sovResource(x uint64) (n int) {
-	return (math_bits.Len64(x|1) + 6) / 7
-}
-func sozResource(x uint64) (n int) {
-	return sovResource(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
 }
-func (m *Unit) Unmarshal(dAtA []byte) error {
+func (m *ResourceUnits) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
 	for iNdEx < l {
@@ -236,17 +1080,17 @@ func (m *Unit) Unmarshal(dAtA []byte) error {
 		fieldNum := int32(wire >> 3)
 		wireType := int(wire & 0x7)
 		if wireType == 4 {
-			return fmt.Errorf("proto: Unit: wiretype end group for non-group")
+			return fmt.Errorf("proto: ResourceUnits: wiretype end group for non-group")
 		}
 		if fieldNum <= 0 {
-			return fmt.Errorf("proto: Unit: illegal tag %d (wire type %d)", fieldNum, wire)
+			return fmt.Errorf("proto: ResourceUnits: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		switch fieldNum {
 		case 1:
-			if wireType != 0 {
+			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field CPU", wireType)
 			}
-			m.CPU = 0
+			var msglen int
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowResource
@@ -256,16 +1100,33 @@ func (m *Unit) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				m.CPU |= uint32(b&0x7F) << shift
+				msglen |= int(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.CPU == nil {
+				m.CPU = &CPU{}
+			}
+			if err := m.CPU.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		case 2:
-			if wireType != 0 {
+			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType)
 			}
-			m.Memory = 0
+			var msglen int
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowResource
@@ -275,16 +1136,33 @@ func (m *Unit) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				m.Memory |= uint64(b&0x7F) << shift
+				msglen |= int(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Memory == nil {
+				m.Memory = &Memory{}
+			}
+			if err := m.Memory.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		case 3:
-			if wireType != 0 {
+			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Storage", wireType)
 			}
-			m.Storage = 0
+			var msglen int
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowResource
@@ -294,11 +1172,28 @@ func (m *Unit) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				m.Storage |= uint64(b&0x7F) << shift
+				msglen |= int(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
+			if msglen < 0 {
+				return ErrInvalidLengthResource
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResource
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Storage == nil {
+				m.Storage = &Storage{}
+			}
+			if err := m.Storage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipResource(dAtA[iNdEx:])
diff --git a/types/resourcevalue.go b/types/resourcevalue.go
new file mode 100644
index 00000000..72af85e7
--- /dev/null
+++ b/types/resourcevalue.go
@@ -0,0 +1,79 @@
+package types
+
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/pkg/errors"
+)
+
+var (
+	errOverflow  = errors.Errorf("resource value overflow")
+	errCannotSub = errors.Errorf("cannot sub resources when lhs does not have same units as rhs")
+)
+
+/*
+ResourceValue the big point of this small change is to ensure math operations on resources
+not resulting with negative value which panic on unsigned types as well as overflow which leads to panic too
+instead reasonable error is returned.
+Each resource using this type as value can take extra advantage of it to check upper bounds
+For example in SDL v1 CPU units were handled as uint32 and operation like math.MaxUint32 + 2
+would cause application to panic. But nowadays
+	const CPULimit = math.MaxUint32
+
+	func (c *CPU) add(rhs CPU) error {
+		res, err := c.Units.add(rhs.Units)
+		if err != nil {
+			return err
+		}
+
+		if res.Units.Value() > CPULimit {
+			return ErrOverflow
+		}
+
+		c.Units = res
+
+		return nil
+	}
+*/
+
+func NewResourceValue(val uint64) ResourceValue {
+	res := ResourceValue{
+		Val: sdk.NewIntFromUint64(val),
+	}
+
+	return res
+}
+
+func (m ResourceValue) Value() uint64 {
+	return m.Val.Uint64()
+}
+
+func (m ResourceValue) equals(rhs ResourceValue) bool {
+	return m.Val.Equal(rhs.Val)
+}
+
+func (m ResourceValue) le(rhs ResourceValue) bool {
+	return m.Val.LTE(rhs.Val)
+}
+
+func (m ResourceValue) add(rhs ResourceValue) (ResourceValue, error) {
+	res := m.Val
+	res = res.Add(rhs.Val)
+
+	if res.Sign() == -1 {
+		return ResourceValue{}, errOverflow
+	}
+
+	return ResourceValue{res}, nil
+}
+
+func (m ResourceValue) sub(rhs ResourceValue) (ResourceValue, error) {
+	res := m.Val
+
+	res = res.Sub(rhs.Val)
+
+	if res.Sign() == -1 {
+		return ResourceValue{}, errCannotSub
+	}
+
+	return ResourceValue{res}, nil
+}
diff --git a/types/resourcevalue.pb.go b/types/resourcevalue.pb.go
new file mode 100644
index 00000000..b14ddc35
--- /dev/null
+++ b/types/resourcevalue.pb.go
@@ -0,0 +1,341 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: akash/resourcevalue.proto
+
+package types
+
+import (
+	fmt "fmt"
+	github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
+	_ "github.com/gogo/protobuf/gogoproto"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+// Unit stores cpu, memory and storage metrics
+type ResourceValue struct {
+	Val github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=val,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"val"`
+}
+
+func (m *ResourceValue) Reset()         { *m = ResourceValue{} }
+func (m *ResourceValue) String() string { return proto.CompactTextString(m) }
+func (*ResourceValue) ProtoMessage()    {}
+func (*ResourceValue) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ade1da729e025ead, []int{0}
+}
+func (m *ResourceValue) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ResourceValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ResourceValue.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ResourceValue) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ResourceValue.Merge(m, src)
+}
+func (m *ResourceValue) XXX_Size() int {
+	return m.Size()
+}
+func (m *ResourceValue) XXX_DiscardUnknown() {
+	xxx_messageInfo_ResourceValue.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ResourceValue proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*ResourceValue)(nil), "akash.ResourceValue")
+}
+
+func init() { proto.RegisterFile("akash/resourcevalue.proto", fileDescriptor_ade1da729e025ead) }
+
+var fileDescriptor_ade1da729e025ead = []byte{
+	// 191 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4c, 0xcc, 0x4e, 0x2c,
+	0xce, 0xd0, 0x2f, 0x4a, 0x2d, 0xce, 0x2f, 0x2d, 0x4a, 0x4e, 0x2d, 0x4b, 0xcc, 0x29, 0x4d, 0xd5,
+	0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x4b, 0x49, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83,
+	0x45, 0xf4, 0x41, 0x2c, 0x88, 0xa4, 0x52, 0x38, 0x17, 0x6f, 0x10, 0x54, 0x4f, 0x18, 0x48, 0x8f,
+	0x90, 0x03, 0x17, 0x73, 0x59, 0x62, 0x8e, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xde, 0x89,
+	0x7b, 0xf2, 0x0c, 0xb7, 0xee, 0xc9, 0xab, 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7,
+	0xe7, 0xea, 0x27, 0xe7, 0x17, 0xe7, 0xe6, 0x17, 0x43, 0x29, 0xdd, 0xe2, 0x94, 0x6c, 0xfd, 0x92,
+	0xca, 0x82, 0xd4, 0x62, 0x3d, 0xcf, 0xbc, 0x92, 0x20, 0x90, 0x56, 0x2b, 0x96, 0x17, 0x0b, 0xe4,
+	0x19, 0x9d, 0xcc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6,
+	0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x16, 0xc9,
+	0xb0, 0xfc, 0xb2, 0xa2, 0xe4, 0x9c, 0x6c, 0x7d, 0x88, 0xe3, 0xc1, 0xe6, 0x24, 0xb1, 0x81, 0x1d,
+	0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x18, 0x8b, 0xa2, 0x76, 0xd2, 0x00, 0x00, 0x00,
+}
+
+func (this *ResourceValue) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*ResourceValue)
+	if !ok {
+		that2, ok := that.(ResourceValue)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if !this.Val.Equal(that1.Val) {
+		return false
+	}
+	return true
+}
+func (m *ResourceValue) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ResourceValue) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ResourceValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	{
+		size := m.Val.Size()
+		i -= size
+		if _, err := m.Val.MarshalTo(dAtA[i:]); err != nil {
+			return 0, err
+		}
+		i = encodeVarintResourcevalue(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintResourcevalue(dAtA []byte, offset int, v uint64) int {
+	offset -= sovResourcevalue(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *ResourceValue) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Val.Size()
+	n += 1 + l + sovResourcevalue(uint64(l))
+	return n
+}
+
+func sovResourcevalue(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozResourcevalue(x uint64) (n int) {
+	return sovResourcevalue(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *ResourceValue) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowResourcevalue
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ResourceValue: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ResourceValue: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Val", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResourcevalue
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthResourcevalue
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthResourcevalue
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Val.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipResourcevalue(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthResourcevalue
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthResourcevalue
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipResourcevalue(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowResourcevalue
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowResourcevalue
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowResourcevalue
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthResourcevalue
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupResourcevalue
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthResourcevalue
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthResourcevalue        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowResourcevalue          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupResourcevalue = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/validation/manifest.go b/validation/manifest.go
index 19c86aba..63ea1f0a 100644
--- a/validation/manifest.go
+++ b/validation/manifest.go
@@ -67,7 +67,7 @@ mainloop:
 }
 
 func validateManifestDeploymentGroup(mgroup types.ResourceGroup, dgroup types.ResourceGroup) error {
-	mlist := make([]types.Resource, len(mgroup.GetResources()))
+	mlist := make([]types.Resources, len(mgroup.GetResources()))
 	copy(mlist, mgroup.GetResources())
 
 mainloop:
@@ -80,7 +80,7 @@ mainloop:
 				continue
 			}
 
-			if !drec.Unit.Equals(mrec.Unit) {
+			if !drec.Resources.Equals(mrec.Resources) {
 				continue
 			}
 
diff --git a/validation/pricing.go b/validation/pricing.go
index f948158b..b92e9396 100644
--- a/validation/pricing.go
+++ b/validation/pricing.go
@@ -11,7 +11,6 @@ import (
 )
 
 func validateGroupPricing(config ValConfig, gspec dtypes.GroupSpec) error {
-
 	var price sdk.Coin
 
 	mem := sdk.NewInt(0)
@@ -31,13 +30,15 @@ func validateGroupPricing(config ValConfig, gspec dtypes.GroupSpec) error {
 			price = price.Add(rprice)
 		}
 
-		mem = mem.Add(
-			sdk.NewIntFromUint64(resource.Unit.Memory).
-				Mul(sdk.NewIntFromUint64(uint64(resource.Count))))
+		memCount := sdk.NewInt(0)
+		if u := resource.Resources.Memory; u != nil {
+			memCount.Add(sdk.NewIntFromUint64(u.Quantity.Value()))
+		}
+
+		mem = mem.Add(memCount.Mul(sdk.NewIntFromUint64(uint64(resource.Count))))
 	}
 
-	minprice := mem.Mul(sdk.NewInt(config.MinGroupMemPrice)).
-		Quo(sdk.NewInt(unit.Gi))
+	minprice := mem.Mul(sdk.NewInt(config.MinGroupMemPrice)).Quo(sdk.NewInt(unit.Gi))
 
 	if price.Amount.LT(minprice) {
 		return errors.Errorf("group %v: price too low (%v >= %v fails)", gspec.GetName(), price, minprice)
@@ -57,6 +58,7 @@ func validateUnitPricing(config ValConfig, rg dtypes.Resource) error {
 	if rg.Price.Amount.GT(sdk.NewIntFromUint64(uint64(config.MaxUnitPrice))) {
 		return errors.Errorf("error: invalid unit price (%v < %v fails)", config.MinUnitPrice, rg.Price)
 	}
+
 	return nil
 }
 
diff --git a/validation/resources.go b/validation/resources.go
index 573d34c8..8c42e6fc 100644
--- a/validation/resources.go
+++ b/validation/resources.go
@@ -20,7 +20,6 @@ func ValidateResourceList(rlist types.ResourceGroup) error {
 }
 
 func validateResourceLists(config ValConfig, rlists []types.ResourceGroup) error {
-
 	if len(rlists) == 0 {
 		return ErrNoGroupsPresent
 	}
@@ -45,6 +44,32 @@ func validateResourceLists(config ValConfig, rlists []types.ResourceGroup) error
 	return nil
 }
 
+type resourceLimits struct {
+	cpu     sdk.Int
+	memory  sdk.Int
+	storage sdk.Int
+}
+
+func newLimits() resourceLimits {
+	return resourceLimits{
+		cpu:     sdk.ZeroInt(),
+		memory:  sdk.ZeroInt(),
+		storage: sdk.ZeroInt(),
+	}
+}
+
+func (u *resourceLimits) add(rhs resourceLimits) {
+	u.cpu = u.cpu.Add(rhs.cpu)
+	u.memory = u.memory.Add(rhs.memory)
+	u.storage = u.storage.Add(rhs.storage)
+}
+
+func (u *resourceLimits) mul(count uint32) {
+	u.cpu = u.cpu.MulRaw(int64(count))
+	u.memory = u.memory.MulRaw(int64(count))
+	u.storage = u.storage.MulRaw(int64(count))
+}
+
 func validateResourceList(config ValConfig, rlist types.ResourceGroup) error {
 	if rlist.GetName() == "" {
 		return ErrGroupEmptyName
@@ -56,56 +81,54 @@ func validateResourceList(config ValConfig, rlist types.ResourceGroup) error {
 		return errors.Errorf("group %v: too many units (%v > %v)", rlist.GetName(), count, config.MaxGroupUnits)
 	}
 
-	var (
-		cpu     = sdk.ZeroUint()
-		mem     = sdk.ZeroUint()
-		storage = sdk.ZeroUint()
-	)
+	limits := newLimits()
 
 	for _, resource := range units {
-
-		if err := validateResourceGroup(config, resource); err != nil {
+		gLimits, err := validateResourceGroup(config, resource)
+		if err != nil {
 			return fmt.Errorf("group %v: %w", rlist.GetName(), err)
 		}
 
-		cpu = cpu.Add(sdk.NewUint(uint64(resource.Unit.CPU)).MulUint64(uint64(resource.Count)))
-		mem = mem.Add(sdk.NewUint(resource.Unit.Memory).MulUint64(uint64(resource.Count)))
-		storage = storage.Add(sdk.NewUint(resource.Unit.Storage).MulUint64(uint64(resource.Count)))
+		gLimits.mul(resource.Count)
+
+		limits.add(gLimits)
 
 		// TODO: validate pricing
 		// if idx == 0 {
 		// 	price = resource.Price
 		// } else {
 		// 	if resource.Price.Denom != price.Denom {
-		// 		return fmt.Errorf("mixed denonimations: (%v != %v)", price.Denom, resource.Price.Denom)
+		// 		return fmt.Errorf("mixed denominations: (%v != %v)", price.Denom, resource.Price.Denom)
 		// 	}
 		// }
 	}
 
-	if cpu.GT(sdk.NewUint(uint64(config.MaxGroupCPU))) || cpu.LTE(sdk.ZeroUint()) {
+	if limits.cpu.GT(sdk.NewInt(config.MaxGroupCPU)) || limits.cpu.LTE(sdk.ZeroInt()) {
 		return errors.Errorf("group %v: invalid total cpu (%v > %v > %v fails)",
-			rlist.GetName(), config.MaxGroupCPU, cpu, 0)
+			rlist.GetName(), config.MaxGroupCPU, limits.cpu, 0)
 	}
 
-	if mem.GT(sdk.NewUint(uint64(config.MaxGroupMemory))) || mem.LTE(sdk.ZeroUint()) {
+	if limits.memory.GT(sdk.NewInt(config.MaxGroupMemory)) || limits.memory.LTE(sdk.ZeroInt()) {
 		return errors.Errorf("group %v: invalid total memory (%v > %v > %v fails)",
-			rlist.GetName(), config.MaxGroupMemory, mem, 0)
+			rlist.GetName(), config.MaxGroupMemory, limits.memory, 0)
 	}
 
-	if storage.GT(sdk.NewUint(uint64(config.MaxGroupStorage))) || storage.LTE(sdk.ZeroUint()) {
+	if limits.storage.GT(sdk.NewInt(config.MaxGroupStorage)) || limits.storage.LTE(sdk.ZeroInt()) {
 		return errors.Errorf("group %v: invalid total storage (%v > %v > %v fails)",
-			rlist.GetName(), config.MaxGroupStorage, storage, 0)
+			rlist.GetName(), config.MaxGroupStorage, limits.storage, 0)
 	}
 
 	return nil
 }
 
-func validateResourceGroup(config ValConfig, rg types.Resource) error {
-	if err := validateResourceUnit(config, rg.Unit); err != nil {
-		return nil
+func validateResourceGroup(config ValConfig, rg types.Resources) (resourceLimits, error) {
+	limits, err := validateResourceUnit(config, rg.Resources)
+	if err != nil {
+		return resourceLimits{}, err
 	}
+
 	if rg.Count > uint32(config.MaxUnitCount) || rg.Count < uint32(config.MinUnitCount) {
-		return errors.Errorf("error: invalid unit count (%v > %v > %v fails)",
+		return resourceLimits{}, errors.Errorf("error: invalid unit count (%v > %v > %v fails)",
 			config.MaxUnitCount, rg.Count, config.MinUnitCount)
 	}
 
@@ -114,21 +137,65 @@ func validateResourceGroup(config ValConfig, rg types.Resource) error {
 	// 	return fmt.Errorf("error: invalid unit price (not positive fails)")
 	// }
 
-	return nil
+	return limits, nil
+}
+
+func validateResourceUnit(config ValConfig, units types.ResourceUnits) (resourceLimits, error) {
+	limits := newLimits()
+
+	val, err := validateCPU(config, units.CPU)
+	if err != nil {
+		return resourceLimits{}, err
+	}
+	limits.cpu = limits.cpu.Add(val)
+
+	val, err = validateMemory(config, units.Memory)
+	if err != nil {
+		return resourceLimits{}, err
+	}
+	limits.memory = limits.memory.Add(val)
+
+	val, err = validateStorage(config, units.Storage)
+	if err != nil {
+		return resourceLimits{}, err
+	}
+	limits.storage = limits.storage.Add(val)
+
+	return limits, nil
 }
 
-func validateResourceUnit(config ValConfig, unit types.Unit) error {
-	if unit.CPU > uint32(config.MaxUnitCPU) || unit.CPU < uint32(config.MinUnitCPU) {
-		return errors.Errorf("error: invalide unit cpu (%v > %v > %v fails)",
-			config.MaxUnitCPU, unit.CPU, config.MinUnitCPU)
+func validateCPU(config ValConfig, u *types.CPU) (sdk.Int, error) {
+	if u == nil {
+		return sdk.Int{}, errors.Errorf("error: invalid unit cpu, cannot be nil")
 	}
-	if unit.Memory > uint64(config.MaxUnitMemory) || unit.Memory < uint64(config.MinUnitMemory) {
-		return errors.Errorf("error: invalid unit memory (%v > %v > %v fails)",
-			config.MaxUnitMemory, unit.Memory, config.MinUnitMemory)
+	if (u.Units.Value() > uint64(config.MaxUnitCPU)) || (u.Units.Value() < uint64(config.MinUnitCPU)) {
+		return sdk.Int{}, errors.Errorf("error: invalid unit cpu (%v > %v > %v fails)",
+			config.MaxUnitCPU, u.Units.Value(), config.MinUnitCPU)
 	}
-	if unit.Storage > uint64(config.MaxUnitStorage) || unit.Storage < uint64(config.MinUnitStorage) {
-		return errors.Errorf("error: invalid unit storage (%v > %v > %v fails)",
-			config.MaxUnitStorage, unit.Storage, config.MinUnitStorage)
+
+	return u.Units.Val, nil
+}
+
+func validateMemory(config ValConfig, u *types.Memory) (sdk.Int, error) {
+	if u == nil {
+		return sdk.Int{}, errors.Errorf("error: invalid unit memory, cannot be nil")
 	}
-	return nil
+	if (u.Quantity.Value() > uint64(config.MaxUnitMemory)) || (u.Quantity.Value() < uint64(config.MinUnitMemory)) {
+		return sdk.Int{}, errors.Errorf("error: invalid unit memory (%v > %v > %v fails)",
+			config.MaxUnitMemory, u.Quantity.Value(), config.MinUnitMemory)
+	}
+
+	return u.Quantity.Val, nil
+}
+
+func validateStorage(config ValConfig, u *types.Storage) (sdk.Int, error) {
+	if u == nil {
+		return sdk.Int{}, errors.Errorf("error: invalid unit storage, cannot be nil")
+	}
+	if (u.Quantity.Value() > uint64(config.MaxUnitStorage)) || (u.Quantity.Value() < uint64(config.MinUnitStorage)) {
+		return sdk.Int{}, errors.Errorf("error: invalid unit storage (%v > %v > %v fails)",
+			config.MaxUnitStorage, u.Quantity.Value(), config.MinUnitStorage)
+	}
+
+	return u.Quantity.Val, nil
 }
diff --git a/x/deployment/types/group.pb.go b/x/deployment/types/group.pb.go
index abfa0a07..322c2702 100644
--- a/x/deployment/types/group.pb.go
+++ b/x/deployment/types/group.pb.go
@@ -7,10 +7,10 @@ import (
 	bytes "bytes"
 	fmt "fmt"
 	github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
-	types "github.com/cosmos/cosmos-sdk/types"
+	types1 "github.com/cosmos/cosmos-sdk/types"
 	_ "github.com/gogo/protobuf/gogoproto"
 	proto "github.com/gogo/protobuf/proto"
-	types1 "github.com/ovrclk/akash/types"
+	types "github.com/ovrclk/akash/types"
 	io "io"
 	math "math"
 	math_bits "math/bits"
@@ -271,9 +271,9 @@ func (m *Group) GetGroupSpec() GroupSpec {
 }
 
 type Resource struct {
-	Unit  types1.Unit `protobuf:"bytes,1,opt,name=unit,proto3" json:"unit" yaml:"unit"`
-	Count uint32      `protobuf:"varint,2,opt,name=count,proto3" json:"count" yaml:"count"`
-	Price types.Coin  `protobuf:"bytes,3,opt,name=price,proto3" json:"price" yaml:"price"`
+	Resources types.ResourceUnits `protobuf:"bytes,1,opt,name=resources,proto3" json:"unit" yaml:"unit"`
+	Count     uint32              `protobuf:"varint,2,opt,name=count,proto3" json:"count" yaml:"count"`
+	Price     types1.Coin         `protobuf:"bytes,3,opt,name=price,proto3" json:"price" yaml:"price"`
 }
 
 func (m *Resource) Reset()         { *m = Resource{} }
@@ -309,11 +309,11 @@ func (m *Resource) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_Resource proto.InternalMessageInfo
 
-func (m *Resource) GetUnit() types1.Unit {
+func (m *Resource) GetResources() types.ResourceUnits {
 	if m != nil {
-		return m.Unit
+		return m.Resources
 	}
-	return types1.Unit{}
+	return types.ResourceUnits{}
 }
 
 func (m *Resource) GetCount() uint32 {
@@ -323,11 +323,11 @@ func (m *Resource) GetCount() uint32 {
 	return 0
 }
 
-func (m *Resource) GetPrice() types.Coin {
+func (m *Resource) GetPrice() types1.Coin {
 	if m != nil {
 		return m.Price
 	}
-	return types.Coin{}
+	return types1.Coin{}
 }
 
 func init() {
@@ -342,64 +342,63 @@ func init() {
 func init() { proto.RegisterFile("akash/deployment/group.proto", fileDescriptor_940c80f4f1e11e54) }
 
 var fileDescriptor_940c80f4f1e11e54 = []byte{
-	// 901 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xcf, 0x6f, 0xe3, 0x44,
-	0x14, 0x8e, 0x13, 0x67, 0xdb, 0x4e, 0x5a, 0x30, 0xc3, 0xc2, 0xa6, 0x2e, 0xcd, 0x58, 0xb3, 0x42,
-	0x8a, 0x58, 0xd5, 0xd6, 0x66, 0x6f, 0x15, 0x97, 0x7a, 0x23, 0x4a, 0x91, 0x96, 0x95, 0x5c, 0x21,
-	0x10, 0x97, 0xe2, 0x78, 0xa6, 0xe9, 0xa8, 0x89, 0xc7, 0xf5, 0xd8, 0x85, 0xfe, 0x07, 0xab, 0x1c,
-	0x10, 0x47, 0x2e, 0x91, 0x2a, 0xf1, 0xcf, 0xec, 0x8d, 0x3d, 0xc2, 0xc5, 0xa0, 0xf4, 0xb2, 0x8a,
-	0x38, 0xe5, 0xc8, 0x09, 0xcd, 0x8c, 0x83, 0xd3, 0x2d, 0x2b, 0x71, 0x4a, 0xde, 0xf7, 0x7d, 0xef,
-	0xcd, 0x8f, 0xf7, 0xcd, 0x33, 0xf8, 0x28, 0x3c, 0x0f, 0xc5, 0x99, 0x47, 0x68, 0x32, 0xe2, 0x57,
-	0x63, 0x1a, 0x67, 0xde, 0x30, 0xe5, 0x79, 0xe2, 0x26, 0x29, 0xcf, 0x38, 0xb4, 0x14, 0xeb, 0x56,
-	0xac, 0x7d, 0x7f, 0xc8, 0x87, 0x5c, 0x91, 0x9e, 0xfc, 0xa7, 0x75, 0xf6, 0x7d, 0x5d, 0x25, 0xa5,
-	0x82, 0xe7, 0x69, 0x44, 0x4b, 0xb4, 0x13, 0x71, 0x31, 0xe6, 0xc2, 0x1b, 0x84, 0x82, 0x7a, 0x97,
-	0x8f, 0x07, 0x34, 0x0b, 0x1f, 0x7b, 0x11, 0x67, 0x71, 0xc9, 0x3f, 0x5c, 0xe5, 0xc3, 0x41, 0xc4,
-	0xfe, 0x15, 0xc9, 0x40, 0x8b, 0xf0, 0x09, 0xd8, 0x7a, 0x26, 0x86, 0x4f, 0x47, 0x5c, 0xd0, 0x43,
-	0xb9, 0x33, 0xf8, 0x39, 0xa8, 0x33, 0xd2, 0x36, 0x1c, 0xa3, 0xdb, 0xea, 0x6d, 0xbb, 0x6f, 0x6e,
-	0xd0, 0x55, 0xa2, 0xa3, 0xbe, 0xbf, 0xfb, 0xb2, 0x40, 0xb5, 0x59, 0x81, 0xea, 0x47, 0xfd, 0x79,
-	0x81, 0xea, 0x8c, 0x2c, 0x0a, 0xb4, 0x71, 0x15, 0x8e, 0x47, 0xfb, 0x98, 0x11, 0x1c, 0xd4, 0x19,
-	0xd9, 0x37, 0x5f, 0x5f, 0xa3, 0x1a, 0xfe, 0xc3, 0x00, 0x6b, 0x65, 0x12, 0xfc, 0x0e, 0x34, 0xf9,
-	0xf7, 0x31, 0x4d, 0x55, 0xf9, 0x4d, 0xff, 0x8b, 0x79, 0x81, 0x34, 0xb0, 0x28, 0xd0, 0xa6, 0x4e,
-	0x56, 0x21, 0xfe, 0xbb, 0x40, 0x7b, 0x43, 0x96, 0x9d, 0xe5, 0x03, 0x37, 0xe2, 0x63, 0xaf, 0x3c,
-	0x88, 0xfe, 0xd9, 0x13, 0xe4, 0xdc, 0xcb, 0xae, 0x12, 0x2a, 0xdc, 0x83, 0x28, 0x3a, 0x20, 0x24,
-	0xa5, 0x42, 0x04, 0xba, 0x0e, 0x7c, 0x02, 0x4c, 0x22, 0xe8, 0x45, 0xbb, 0xee, 0x18, 0x5d, 0xd3,
-	0x47, 0xb3, 0x02, 0x99, 0xfd, 0x63, 0x7a, 0x31, 0x2f, 0x90, 0xc2, 0x17, 0x05, 0x6a, 0xe9, 0x75,
-	0x64, 0x84, 0x03, 0x05, 0xca, 0xa4, 0xa1, 0x4c, 0x6a, 0x38, 0x46, 0x77, 0x4b, 0x27, 0x1d, 0x96,
-	0x49, 0xc3, 0x5b, 0x49, 0x43, 0x9d, 0x24, 0x7f, 0xf6, 0xd7, 0x7f, 0xbe, 0x46, 0xb5, 0xd7, 0xd7,
-	0xc8, 0xc0, 0x3f, 0x36, 0xc0, 0x86, 0x3a, 0xe1, 0x71, 0x42, 0x23, 0xf8, 0x08, 0x98, 0x71, 0x38,
-	0xa6, 0xea, 0x88, 0x1b, 0xfe, 0x03, 0x59, 0x44, 0xc6, 0x55, 0x11, 0x19, 0xe1, 0x40, 0x81, 0x30,
-	0x03, 0x9b, 0x29, 0xbd, 0xc8, 0x59, 0x4a, 0xe5, 0xe5, 0x8a, 0x76, 0xdd, 0x69, 0x74, 0x5b, 0xbd,
-	0x87, 0xae, 0x3e, 0xa9, 0x2b, 0x3b, 0xe7, 0xaa, 0x66, 0x95, 0x9d, 0x73, 0x0f, 0xb2, 0x2c, 0x65,
-	0x83, 0x3c, 0xa3, 0xfe, 0x23, 0xd9, 0x80, 0x79, 0x81, 0x6e, 0x15, 0x58, 0x14, 0xe8, 0x7d, 0xbd,
-	0xca, 0x2a, 0x8a, 0x83, 0x5b, 0x22, 0x78, 0x02, 0x36, 0x96, 0x56, 0x12, 0xed, 0x86, 0x5a, 0xd2,
-	0xbe, 0xdb, 0xe9, 0xa0, 0x94, 0xf8, 0x1f, 0x97, 0x2b, 0x55, 0x49, 0x8b, 0x02, 0x59, 0xcb, 0x65,
-	0x4a, 0x08, 0x07, 0x15, 0x0d, 0x05, 0xb0, 0x78, 0x4a, 0x68, 0xea, 0x33, 0xd2, 0xcf, 0xd3, 0x30,
-	0x63, 0x3c, 0x6e, 0x9b, 0x8e, 0xd1, 0x6d, 0xf8, 0x87, 0xb3, 0x02, 0x59, 0xcf, 0xdf, 0xe0, 0xe6,
-	0x05, 0x82, 0x4a, 0xbf, 0x37, 0x60, 0x64, 0x8f, 0x94, 0xe8, 0xa2, 0x40, 0xdb, 0xa5, 0x27, 0xee,
-	0x70, 0x38, 0xb8, 0xb3, 0xc0, 0xfe, 0xfa, 0x0b, 0xdd, 0x90, 0x1a, 0xfe, 0xab, 0x01, 0x9a, 0xda,
-	0xcc, 0xdf, 0x80, 0x75, 0xf5, 0xde, 0x4e, 0xfe, 0x8f, 0xa5, 0x71, 0x69, 0xe9, 0xa5, 0x5d, 0xff,
-	0xcb, 0xd7, 0x6b, 0xaa, 0xdc, 0x11, 0x81, 0x5f, 0x82, 0xa6, 0xc8, 0xc2, 0x8c, 0x2a, 0xa7, 0xbd,
-	0xd3, 0xdb, 0x7d, 0x4b, 0x59, 0xf7, 0x58, 0x8a, 0xfc, 0x6d, 0xe9, 0x74, 0xa5, 0xaf, 0x9c, 0xae,
-	0x42, 0x1c, 0x68, 0x18, 0x7e, 0x0d, 0x80, 0xde, 0xa9, 0x48, 0x68, 0xa4, 0x9c, 0xd8, 0xea, 0xed,
-	0xbc, 0xa5, 0xa8, 0xf4, 0x99, 0xbf, 0x53, 0x76, 0xc5, 0x94, 0x09, 0x95, 0xbb, 0x64, 0x84, 0x83,
-	0x8d, 0xe1, 0x52, 0x87, 0x7f, 0x37, 0x40, 0x53, 0x6d, 0x02, 0x62, 0xb0, 0xc6, 0xe2, 0xcb, 0x70,
-	0xc4, 0x88, 0x55, 0xb3, 0x3f, 0x98, 0x4c, 0x9d, 0xf7, 0x74, 0x35, 0x49, 0x1e, 0x69, 0x02, 0x3e,
-	0x00, 0x26, 0x4f, 0x68, 0x6c, 0x19, 0xf6, 0xd6, 0x64, 0xea, 0x68, 0x5b, 0x3f, 0x4f, 0x68, 0x0c,
-	0x77, 0xc1, 0x9a, 0xba, 0x71, 0x4a, 0xac, 0xba, 0x6d, 0x4d, 0xa6, 0xce, 0xa6, 0xe6, 0x34, 0x26,
-	0xe9, 0x71, 0x98, 0x45, 0x67, 0x94, 0x58, 0x8d, 0x15, 0xfa, 0x99, 0xc6, 0x60, 0x0f, 0x40, 0x16,
-	0x8b, 0xfc, 0xf4, 0x94, 0x45, 0x8c, 0xc6, 0xd9, 0xc9, 0x69, 0x1e, 0x13, 0x61, 0x99, 0xb6, 0x3d,
-	0x99, 0x3a, 0x1f, 0xea, 0xeb, 0x5e, 0xa1, 0x3f, 0x93, 0x2c, 0xdc, 0x01, 0xf7, 0x22, 0x39, 0x96,
-	0x88, 0xd5, 0xb4, 0xdf, 0x9d, 0x4c, 0x9d, 0x96, 0xd2, 0xa9, 0x49, 0x45, 0x6c, 0xf3, 0xc5, 0x2f,
-	0x9d, 0x5a, 0x39, 0x61, 0x7e, 0x35, 0xc0, 0xfa, 0xd2, 0xac, 0xf0, 0x53, 0x60, 0xe6, 0x31, 0xcb,
-	0xca, 0x6e, 0xb7, 0xca, 0x1b, 0xfc, 0x2a, 0x66, 0x59, 0x75, 0x63, 0x52, 0x50, 0xdd, 0x98, 0x8c,
-	0x70, 0xa0, 0x40, 0xe8, 0x81, 0x66, 0xc4, 0xf3, 0x38, 0x53, 0x5d, 0xdd, 0xd2, 0x6d, 0x53, 0x40,
-	0xd5, 0x36, 0x15, 0xe2, 0x40, 0xc3, 0xd2, 0x06, 0x49, 0xca, 0x22, 0x5a, 0x76, 0x6c, 0xfb, 0xd6,
-	0xcb, 0x5d, 0x3e, 0xda, 0xa7, 0x9c, 0xc5, 0x7a, 0x60, 0xca, 0x7a, 0x4a, 0x5f, 0xd5, 0x53, 0x21,
-	0x0e, 0x34, 0xac, 0x4e, 0x64, 0xf8, 0xfd, 0x97, 0xb3, 0x8e, 0xf1, 0x6a, 0xd6, 0x31, 0xfe, 0x9c,
-	0x75, 0x8c, 0x9f, 0x6e, 0x3a, 0xb5, 0x57, 0x37, 0x9d, 0xda, 0x6f, 0x37, 0x9d, 0xda, 0xb7, 0x9f,
-	0xac, 0x4c, 0x45, 0x7e, 0x99, 0x46, 0xa3, 0x73, 0x4f, 0x7f, 0x1b, 0x7e, 0x58, 0xfd, 0xc6, 0xa8,
-	0xe9, 0x38, 0xb8, 0xa7, 0x26, 0xfc, 0x93, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x82, 0x0e, 0x6a,
-	0xdd, 0x84, 0x06, 0x00, 0x00,
+	// 889 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0x41, 0x6f, 0xe3, 0x44,
+	0x14, 0x8e, 0x13, 0x67, 0xdb, 0x4e, 0x5a, 0x30, 0x43, 0x97, 0x4d, 0x5d, 0x9a, 0xb1, 0x46, 0x42,
+	0x8a, 0x58, 0xd5, 0xd6, 0x66, 0x6f, 0xbd, 0xd5, 0x1b, 0x51, 0x8a, 0xb4, 0x54, 0x72, 0x85, 0x40,
+	0x5c, 0x8a, 0xe3, 0x99, 0xa6, 0xa3, 0x26, 0x1e, 0xd7, 0x63, 0x17, 0xfa, 0x0f, 0x56, 0x39, 0x71,
+	0xe4, 0x12, 0xa9, 0x12, 0x7f, 0x66, 0x8f, 0x7b, 0x84, 0x8b, 0x41, 0xe9, 0x65, 0x15, 0x71, 0xca,
+	0x11, 0x2e, 0x68, 0x66, 0x1c, 0x39, 0xdd, 0xb2, 0x12, 0x27, 0xf7, 0x7d, 0xdf, 0x7b, 0xdf, 0xcc,
+	0xbc, 0xf7, 0xf5, 0x05, 0x7c, 0x1a, 0x5e, 0x86, 0xe2, 0xc2, 0x23, 0x34, 0x19, 0xf1, 0x9b, 0x31,
+	0x8d, 0x33, 0x6f, 0x98, 0xf2, 0x3c, 0x71, 0x93, 0x94, 0x67, 0x1c, 0x5a, 0x8a, 0x75, 0x2b, 0xd6,
+	0xde, 0x1e, 0xf2, 0x21, 0x57, 0xa4, 0x27, 0xff, 0xd2, 0x79, 0xf6, 0x63, 0xad, 0x12, 0x66, 0x59,
+	0xca, 0x06, 0x79, 0x46, 0x4b, 0x78, 0x5b, 0xc3, 0x29, 0x15, 0x3c, 0x4f, 0xa3, 0x25, 0xda, 0x89,
+	0xb8, 0x18, 0x73, 0xe1, 0x0d, 0x42, 0x41, 0xbd, 0xeb, 0x67, 0x03, 0x9a, 0x85, 0xcf, 0xbc, 0x88,
+	0xb3, 0x58, 0xf3, 0xf8, 0x0c, 0x6c, 0xbd, 0x14, 0xc3, 0x17, 0x23, 0x2e, 0xe8, 0x91, 0xbc, 0x0b,
+	0xfc, 0x12, 0xd4, 0x19, 0x69, 0x1b, 0x8e, 0xd1, 0x6d, 0xf5, 0x76, 0xdc, 0x77, 0xaf, 0xe4, 0xaa,
+	0xa4, 0xe3, 0xbe, 0xbf, 0xf7, 0xba, 0x40, 0xb5, 0x59, 0x81, 0xea, 0xc7, 0xfd, 0x79, 0x81, 0xea,
+	0x8c, 0x2c, 0x0a, 0xb4, 0x71, 0x13, 0x8e, 0x47, 0x07, 0x98, 0x11, 0x1c, 0xd4, 0x19, 0x39, 0x30,
+	0xdf, 0xde, 0xa2, 0x1a, 0xfe, 0xc3, 0x00, 0x6b, 0x65, 0x11, 0xfc, 0x01, 0x34, 0xf9, 0x8f, 0x31,
+	0x4d, 0x95, 0xfc, 0xa6, 0xff, 0xd5, 0xbc, 0x40, 0x1a, 0x58, 0x14, 0x68, 0x53, 0x17, 0xab, 0x10,
+	0xff, 0x5d, 0xa0, 0xfd, 0x21, 0xcb, 0x2e, 0xf2, 0x81, 0x1b, 0xf1, 0xb1, 0x57, 0xbe, 0x41, 0x7f,
+	0xf6, 0x05, 0xb9, 0xf4, 0xb2, 0x9b, 0x84, 0x0a, 0xf7, 0x30, 0x8a, 0x0e, 0x09, 0x49, 0xa9, 0x10,
+	0x81, 0xd6, 0x81, 0xcf, 0x81, 0x49, 0x04, 0xbd, 0x6a, 0xd7, 0x1d, 0xa3, 0x6b, 0xfa, 0x68, 0x56,
+	0x20, 0xb3, 0x7f, 0x4a, 0xaf, 0xe6, 0x05, 0x52, 0xf8, 0xa2, 0x40, 0x2d, 0x7d, 0x8e, 0x8c, 0x70,
+	0xa0, 0x40, 0x59, 0x34, 0x94, 0x45, 0x0d, 0xc7, 0xe8, 0x6e, 0xe9, 0xa2, 0xa3, 0xb2, 0x68, 0x78,
+	0xaf, 0x68, 0xa8, 0x8b, 0xe4, 0xe7, 0x60, 0xfd, 0x97, 0x5b, 0x54, 0x7b, 0x7b, 0x8b, 0x0c, 0xfc,
+	0x4f, 0x1d, 0x6c, 0xa8, 0x17, 0x9e, 0x26, 0x34, 0x82, 0x4f, 0x81, 0x19, 0x87, 0x63, 0xaa, 0x9e,
+	0xb8, 0xe1, 0x3f, 0x91, 0x22, 0x32, 0xae, 0x44, 0x64, 0x84, 0x03, 0x05, 0xc2, 0x10, 0x6c, 0xa6,
+	0xf4, 0x2a, 0x67, 0x29, 0x95, 0xcd, 0x15, 0xed, 0xba, 0xd3, 0xe8, 0xb6, 0x7a, 0x56, 0xd9, 0xf6,
+	0xc3, 0xe5, 0x84, 0xfd, 0xa7, 0xb2, 0xdb, 0xf3, 0x02, 0xdd, 0xcb, 0x5e, 0x14, 0xe8, 0x63, 0x2d,
+	0xb9, 0x8a, 0xe2, 0xe0, 0x5e, 0x12, 0x3c, 0x03, 0x1b, 0x4b, 0x4b, 0x88, 0x76, 0x43, 0xe9, 0xdb,
+	0x0f, 0xc7, 0x1a, 0x94, 0x29, 0xfe, 0x67, 0xe5, 0x49, 0x55, 0xd1, 0xa2, 0x40, 0xd6, 0xf2, 0x98,
+	0x12, 0xc2, 0x41, 0x45, 0x43, 0x01, 0x2c, 0x9e, 0x12, 0x9a, 0xfa, 0x8c, 0xf4, 0xf3, 0x34, 0xcc,
+	0x18, 0x8f, 0xdb, 0xa6, 0x63, 0x74, 0x1b, 0xfe, 0xd1, 0xac, 0x40, 0xd6, 0xc9, 0x3b, 0xdc, 0xbc,
+	0x40, 0x50, 0xe5, 0xef, 0x0f, 0x18, 0xd9, 0x27, 0x25, 0xba, 0x28, 0xd0, 0x4e, 0x69, 0x80, 0x07,
+	0x1c, 0x0e, 0x1e, 0x1c, 0x70, 0xb0, 0xfe, 0x4a, 0x77, 0xbf, 0x86, 0xff, 0x6a, 0x80, 0xa6, 0x76,
+	0xee, 0x77, 0x60, 0x5d, 0xfd, 0x3b, 0x9d, 0xfd, 0x1f, 0xff, 0xe2, 0xd2, 0xbf, 0x4b, 0x6f, 0xfe,
+	0x97, 0x89, 0xd7, 0x94, 0xdc, 0x31, 0x81, 0x5f, 0x83, 0xa6, 0xc8, 0xc2, 0x8c, 0x2a, 0x5b, 0x7d,
+	0xd0, 0xdb, 0x7b, 0x8f, 0xac, 0x7b, 0x2a, 0x93, 0xfc, 0x1d, 0x69, 0x6b, 0x95, 0x5f, 0xd9, 0x5a,
+	0x85, 0x38, 0xd0, 0x30, 0xfc, 0x16, 0x00, 0x7d, 0x53, 0x91, 0xd0, 0x48, 0xd9, 0xae, 0xd5, 0xdb,
+	0x7d, 0x8f, 0xa8, 0x34, 0x95, 0xbf, 0x5b, 0x4e, 0xc5, 0x94, 0x05, 0x95, 0x95, 0x64, 0x84, 0x83,
+	0x8d, 0xe1, 0x32, 0x0f, 0xff, 0x6e, 0x80, 0xa6, 0xba, 0x04, 0xc4, 0x60, 0x8d, 0xc5, 0xd7, 0xe1,
+	0x88, 0x11, 0xab, 0x66, 0x3f, 0x9e, 0x4c, 0x9d, 0x8f, 0xb4, 0x9a, 0x24, 0x8f, 0x35, 0x01, 0x9f,
+	0x00, 0x93, 0x27, 0x34, 0xb6, 0x0c, 0x7b, 0x6b, 0x32, 0x75, 0xb4, 0x87, 0x4f, 0x12, 0x1a, 0xc3,
+	0x3d, 0xb0, 0xa6, 0x3a, 0x4e, 0x89, 0x55, 0xb7, 0xad, 0xc9, 0xd4, 0xd9, 0xd4, 0x9c, 0xc6, 0x24,
+	0x3d, 0x0e, 0xb3, 0xe8, 0x82, 0x12, 0xab, 0xb1, 0x42, 0xbf, 0xd4, 0x18, 0xec, 0x01, 0xc8, 0x62,
+	0x91, 0x9f, 0x9f, 0xb3, 0x88, 0xd1, 0x38, 0x3b, 0x3b, 0xcf, 0x63, 0x22, 0x2c, 0xd3, 0xb6, 0x27,
+	0x53, 0xe7, 0x13, 0xdd, 0xee, 0x15, 0xfa, 0x0b, 0xc9, 0xc2, 0x5d, 0xf0, 0x28, 0x92, 0x3b, 0x88,
+	0x58, 0x4d, 0xfb, 0xc3, 0xc9, 0xd4, 0x69, 0xa9, 0x3c, 0xb5, 0x96, 0x88, 0x6d, 0xbe, 0xfa, 0xb5,
+	0x53, 0x2b, 0xd7, 0xc9, 0xcc, 0x00, 0xeb, 0x4b, 0xb3, 0xc2, 0x93, 0x55, 0x6f, 0xeb, 0x91, 0x6f,
+	0x97, 0x6d, 0x5c, 0xe6, 0x7c, 0x13, 0xb3, 0x4c, 0x54, 0xfd, 0xcb, 0x63, 0x96, 0x55, 0xfd, 0x93,
+	0xd1, 0x3d, 0x2f, 0x7b, 0xa0, 0x19, 0xf1, 0x3c, 0xce, 0xd4, 0xa0, 0xb7, 0xf4, 0x24, 0x15, 0x50,
+	0x4d, 0x52, 0x85, 0x38, 0xd0, 0xb0, 0x74, 0x46, 0x92, 0xb2, 0x88, 0x96, 0x43, 0xdc, 0x71, 0xf5,
+	0x8e, 0x72, 0xe5, 0xba, 0x75, 0xcb, 0x75, 0xeb, 0xbe, 0xe0, 0x2c, 0xd6, 0x0b, 0x53, 0xea, 0xa9,
+	0xfc, 0x4a, 0x4f, 0x85, 0x38, 0xd0, 0xb0, 0x7a, 0xa4, 0xe1, 0xf7, 0x5f, 0xcf, 0x3a, 0xc6, 0x9b,
+	0x59, 0xc7, 0xf8, 0x73, 0xd6, 0x31, 0x7e, 0xbe, 0xeb, 0xd4, 0xde, 0xdc, 0x75, 0x6a, 0xbf, 0xdd,
+	0x75, 0x6a, 0xdf, 0x7f, 0xbe, 0xb2, 0x15, 0xf9, 0x75, 0x1a, 0x8d, 0x2e, 0x3d, 0xbd, 0xf6, 0x7f,
+	0x5a, 0xfd, 0x55, 0x51, 0xdb, 0x71, 0xf0, 0x48, 0x6d, 0xf8, 0xe7, 0xff, 0x06, 0x00, 0x00, 0xff,
+	0xff, 0x7a, 0xb3, 0x44, 0x46, 0x76, 0x06, 0x00, 0x00,
 }
 
 func (this *GroupID) Equal(that interface{}) bool {
@@ -451,7 +450,7 @@ func (this *Resource) Equal(that interface{}) bool {
 	} else if this == nil {
 		return false
 	}
-	if !this.Unit.Equal(&that1.Unit) {
+	if !this.Resources.Equal(&that1.Resources) {
 		return false
 	}
 	if this.Count != that1.Count {
@@ -682,7 +681,7 @@ func (m *Resource) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		dAtA[i] = 0x10
 	}
 	{
-		size, err := m.Unit.MarshalToSizedBuffer(dAtA[:i])
+		size, err := m.Resources.MarshalToSizedBuffer(dAtA[:i])
 		if err != nil {
 			return 0, err
 		}
@@ -785,7 +784,7 @@ func (m *Resource) Size() (n int) {
 	}
 	var l int
 	_ = l
-	l = m.Unit.Size()
+	l = m.Resources.Size()
 	n += 1 + l + sovGroup(uint64(l))
 	if m.Count != 0 {
 		n += 1 + sovGroup(uint64(m.Count))
@@ -1353,7 +1352,7 @@ func (m *Resource) Unmarshal(dAtA []byte) error {
 		switch fieldNum {
 		case 1:
 			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Unit", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field Resources", wireType)
 			}
 			var msglen int
 			for shift := uint(0); ; shift += 7 {
@@ -1380,7 +1379,7 @@ func (m *Resource) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			if err := m.Unit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+			if err := m.Resources.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
 			iNdEx = postIndex
diff --git a/x/deployment/types/types.go b/x/deployment/types/types.go
index 1a40f1f8..5989ae23 100644
--- a/x/deployment/types/types.go
+++ b/x/deployment/types/types.go
@@ -12,10 +12,10 @@ import (
 // DefaultOrderBiddingDuration is the default time limit for an Order being active.
 // After the duration, the Order is automatically closed.
 // ( 24(hr) * 3600(seconds per hour) ) / 7s-Block
-const DefaultOrderBiddingDuration int64 = int64(12342)
+const DefaultOrderBiddingDuration = int64(12342)
 
 // MaxBiddingDuration is roughly 30 days of block height
-const MaxBiddingDuration int64 = DefaultOrderBiddingDuration * int64(30)
+const MaxBiddingDuration = DefaultOrderBiddingDuration * int64(30)
 
 // //go:generate stringer -linecomment -output=autogen_stringer.go -type=DeploymentState,GroupState
 
@@ -78,14 +78,15 @@ func (g GroupSpec) ValidateBasic() error {
 }
 
 // GetResources method returns resources list in group
-func (g GroupSpec) GetResources() []types.Resource {
-	resources := make([]types.Resource, 0, len(g.Resources))
+func (g GroupSpec) GetResources() []types.Resources {
+	resources := make([]types.Resources, 0, len(g.Resources))
 	for _, r := range g.Resources {
-		resources = append(resources, types.Resource{
-			Unit:  r.Unit,
-			Count: r.Count,
+		resources = append(resources, types.Resources{
+			Resources: r.Resources,
+			Count:     r.Count,
 		})
 	}
+
 	return resources
 }
 
@@ -108,17 +109,8 @@ func (g GroupSpec) Price() sdk.Coin {
 }
 
 // MatchAttributes method compares provided attributes with specific group attributes
-func (g GroupSpec) MatchAttributes(attrs []sdk.Attribute) bool {
-loop:
-	for _, req := range g.Requirements {
-		for _, attr := range attrs {
-			if req.Key == attr.Key && req.Value == attr.Value {
-				continue loop
-			}
-		}
-		return false
-	}
-	return true
+func (g GroupSpec) MatchAttributes(attrs []types.Attribute) bool {
+	return types.AttributesSubsetOf(g.Requirements, attrs)
 }
 
 // ID method returns GroupID details of specific group
@@ -152,8 +144,20 @@ func (g Group) GetName() string {
 	return g.GroupSpec.Name
 }
 
+// Resource stores resources group, count (amount of group) and price of each group
+// type Resource struct {
+// 	Resources types.ResourceUnits `json:"resources"`
+// 	Price     sdk.Coin            `json:"price"`
+// 	Count     uint32              `json:"count"`
+// }
+
+// GetUnits method returns unit of resource
+// func (r Resource) GetResources() types.ResourceUnits {
+// 	return r.Resources
+// }
+
 // GetResources method returns resources list in group
-func (g Group) GetResources() []types.Resource {
+func (g Group) GetResources() []types.Resources {
 	return g.GroupSpec.GetResources()
 }
 
diff --git a/x/market/types/types.go b/x/market/types/types.go
index 0682b7c3..54941418 100644
--- a/x/market/types/types.go
+++ b/x/market/types/types.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/ovrclk/akash/types"
 	"github.com/ovrclk/akash/validation"
 	"github.com/pkg/errors"
 	yaml "gopkg.in/yaml.v2"
@@ -66,7 +67,7 @@ func (o Order) Price() sdk.Coin {
 }
 
 // MatchAttributes method compares provided attributes with specific order attributes
-func (o Order) MatchAttributes(attrs []sdk.Attribute) bool {
+func (o Order) MatchAttributes(attrs []types.Attribute) bool {
 	return o.Spec.MatchAttributes(attrs)
 }
 
diff --git a/x/provider/config/config.go b/x/provider/config/config.go
index 5df27e5c..24fea541 100644
--- a/x/provider/config/config.go
+++ b/x/provider/config/config.go
@@ -3,19 +3,19 @@ package config
 import (
 	"io/ioutil"
 
-	sdk "github.com/cosmos/cosmos-sdk/types"
+	"gopkg.in/yaml.v3"
 
-	"gopkg.in/yaml.v2"
+	"github.com/ovrclk/akash/types"
 )
 
 // Config is the struct that stores provider config
 type Config struct {
-	Host       string          `json:"host"`
-	Attributes []sdk.Attribute `json:"attributes"`
+	Host       string            `json:"host"`
+	Attributes []types.Attribute `json:"attributes"`
 }
 
 // GetAttributes returns config attributes into key value pairs
-func (c Config) GetAttributes() []sdk.Attribute {
+func (c Config) GetAttributes() []types.Attribute {
 	return c.Attributes
 }
 
diff --git a/x/provider/types/provider.pb.go b/x/provider/types/provider.pb.go
index c7fa63a2..ec5e8341 100644
--- a/x/provider/types/provider.pb.go
+++ b/x/provider/types/provider.pb.go
@@ -6,9 +6,9 @@ package types
 import (
 	fmt "fmt"
 	github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
-	types "github.com/cosmos/cosmos-sdk/types"
 	_ "github.com/gogo/protobuf/gogoproto"
 	proto "github.com/gogo/protobuf/proto"
+	types "github.com/ovrclk/akash/types"
 	io "io"
 	math "math"
 	math_bits "math/bits"
@@ -262,34 +262,32 @@ func init() {
 func init() { proto.RegisterFile("akash/provider/provider.proto", fileDescriptor_f678b91550e081a6) }
 
 var fileDescriptor_f678b91550e081a6 = []byte{
-	// 417 bytes of a gzipped FileDescriptorProto
+	// 390 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0xcc, 0x4e, 0x2c,
 	0xce, 0xd0, 0x2f, 0x28, 0xca, 0x2f, 0xcb, 0x4c, 0x49, 0x2d, 0x82, 0x33, 0xf4, 0x0a, 0x8a, 0xf2,
 	0x4b, 0xf2, 0x85, 0xf8, 0xc0, 0xd2, 0x7a, 0x30, 0x51, 0x29, 0x91, 0xf4, 0xfc, 0xf4, 0x7c, 0xb0,
-	0x94, 0x3e, 0x88, 0x05, 0x51, 0x25, 0xa5, 0x9c, 0x9c, 0x5f, 0x9c, 0x9b, 0x5f, 0xac, 0x9f, 0x94,
-	0x58, 0x9c, 0xaa, 0x9f, 0x98, 0x94, 0x9c, 0xa9, 0x5f, 0x66, 0x98, 0x94, 0x5a, 0x92, 0x68, 0x08,
-	0xe6, 0x40, 0x14, 0x29, 0xed, 0x63, 0xe2, 0x12, 0xf4, 0x2d, 0x4e, 0x77, 0x2e, 0x4a, 0x4d, 0x2c,
-	0x49, 0x0d, 0x80, 0x1a, 0x28, 0x94, 0xc0, 0xc5, 0x9a, 0x5f, 0x9e, 0x97, 0x5a, 0x24, 0xc1, 0xa8,
-	0xc0, 0xa8, 0xc1, 0xe3, 0xe4, 0xf5, 0xea, 0x9e, 0x3c, 0x44, 0xe0, 0xd3, 0x3d, 0x79, 0x9e, 0xca,
-	0xc4, 0xdc, 0x1c, 0x2b, 0x25, 0x30, 0x57, 0xe9, 0xd7, 0x3d, 0x79, 0xdd, 0xf4, 0xcc, 0x92, 0x8c,
-	0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x8d, 0x10, 0x4a, 0xb7, 0x38, 0x25, 0x5b, 0xbf,
-	0xa4, 0xb2, 0x20, 0xb5, 0x58, 0xcf, 0x31, 0x39, 0xd9, 0x31, 0x25, 0xa5, 0x28, 0xb5, 0xb8, 0x38,
-	0x08, 0x62, 0x8e, 0x90, 0x2b, 0x17, 0x47, 0x46, 0x7e, 0x71, 0x49, 0x7c, 0x69, 0x51, 0xa6, 0x04,
-	0x93, 0x02, 0xa3, 0x06, 0xa7, 0x93, 0xd6, 0xa3, 0x7b, 0xf2, 0xec, 0x1e, 0xf9, 0xc5, 0x25, 0xa1,
-	0x41, 0x9e, 0xaf, 0xee, 0xc9, 0x83, 0xa5, 0x75, 0x4b, 0x8b, 0x32, 0x3f, 0xdd, 0x93, 0xe7, 0x87,
-	0x58, 0x09, 0x13, 0x51, 0x0a, 0x62, 0x07, 0x31, 0x43, 0x8b, 0x32, 0x85, 0xaa, 0xb8, 0xb8, 0x12,
-	0x4b, 0x4a, 0x8a, 0x32, 0x93, 0x4a, 0x4b, 0x52, 0x8b, 0x25, 0x98, 0x15, 0x98, 0x35, 0xb8, 0x8d,
-	0x94, 0xf5, 0x20, 0xf6, 0xeb, 0x81, 0x3c, 0xae, 0x07, 0xf6, 0x2b, 0xd4, 0xe3, 0x7a, 0x8e, 0x30,
-	0xb5, 0x4e, 0xa6, 0x27, 0xee, 0xc9, 0x33, 0xbc, 0xba, 0x27, 0x8f, 0xa4, 0xfd, 0xd3, 0x3d, 0x79,
-	0x41, 0x88, 0x45, 0x08, 0x31, 0xa5, 0x55, 0xf7, 0xe5, 0xb9, 0xe0, 0xba, 0x8a, 0x83, 0x90, 0x94,
-	0x5b, 0xb1, 0xbc, 0x58, 0x20, 0xcf, 0x00, 0x0b, 0xc0, 0xd0, 0x82, 0x94, 0xd1, 0x00, 0x24, 0x2b,
-	0x00, 0xab, 0xc1, 0xe1, 0xe7, 0x92, 0x9a, 0x93, 0x4a, 0xcf, 0xf0, 0x83, 0x5a, 0xbe, 0x93, 0x89,
-	0x8b, 0x63, 0x34, 0xd2, 0x48, 0x89, 0x34, 0x8e, 0x19, 0x0b, 0xe4, 0x19, 0x40, 0x61, 0xe7, 0xe4,
-	0x74, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c,
-	0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x1a, 0x48, 0x81, 0x93, 0x5f,
-	0x56, 0x94, 0x9c, 0x93, 0xad, 0x0f, 0x29, 0xd0, 0x2a, 0x10, 0x45, 0x1a, 0x38, 0x88, 0x92, 0xd8,
-	0xc0, 0xa5, 0x90, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xb2, 0x27, 0x6b, 0x9c, 0xf1, 0x04, 0x00,
-	0x00,
+	0x94, 0x3e, 0x88, 0x05, 0x51, 0x25, 0x25, 0x0a, 0x31, 0x24, 0xb1, 0xa4, 0xa4, 0x28, 0x33, 0xa9,
+	0xb4, 0x24, 0x15, 0x22, 0xac, 0xb4, 0x9a, 0x89, 0x4b, 0xd0, 0xb7, 0x38, 0xdd, 0xb9, 0x28, 0x35,
+	0xb1, 0x24, 0x35, 0x00, 0x6a, 0x84, 0x50, 0x02, 0x17, 0x6b, 0x7e, 0x79, 0x5e, 0x6a, 0x91, 0x04,
+	0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd7, 0xab, 0x7b, 0xf2, 0x10, 0x81, 0x4f, 0xf7, 0xe4, 0x79,
+	0x2a, 0x13, 0x73, 0x73, 0xac, 0x94, 0xc0, 0x5c, 0xa5, 0x5f, 0xf7, 0xe4, 0x75, 0xd3, 0x33, 0x4b,
+	0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xa1, 0x94,
+	0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x63, 0x72, 0xb2, 0x63, 0x4a,
+	0x4a, 0x51, 0x6a, 0x71, 0x71, 0x10, 0xc4, 0x1c, 0x21, 0x57, 0x2e, 0x8e, 0x8c, 0xfc, 0xe2, 0x92,
+	0xf8, 0xd2, 0xa2, 0x4c, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0xad, 0x47, 0xf7, 0xe4, 0xd9,
+	0x3d, 0xf2, 0x8b, 0x4b, 0x42, 0x83, 0x3c, 0x5f, 0xdd, 0x93, 0x07, 0x4b, 0xeb, 0x96, 0x16, 0x65,
+	0x7e, 0xba, 0x27, 0xcf, 0x0f, 0xb1, 0x12, 0x26, 0xa2, 0x14, 0xc4, 0x0e, 0x62, 0x86, 0x16, 0x65,
+	0x0a, 0xa5, 0x73, 0x71, 0xc1, 0x7d, 0x54, 0x2c, 0xc1, 0xac, 0xc0, 0xac, 0xc1, 0x6d, 0x24, 0xa0,
+	0x07, 0x09, 0x10, 0x47, 0x98, 0x84, 0x93, 0xe9, 0x89, 0x7b, 0xf2, 0x0c, 0xaf, 0xee, 0xc9, 0x23,
+	0xa9, 0xfd, 0x74, 0x4f, 0x5e, 0x10, 0x62, 0x2a, 0x42, 0x4c, 0x69, 0xd5, 0x7d, 0x79, 0x2e, 0xb8,
+	0xae, 0xe2, 0x20, 0x24, 0xe5, 0x56, 0x2c, 0x2f, 0x16, 0xc8, 0x33, 0xc0, 0x42, 0x2b, 0xb4, 0x20,
+	0x65, 0x34, 0xb4, 0x08, 0x87, 0x56, 0x35, 0x38, 0xb0, 0x5c, 0x52, 0x73, 0x52, 0xe9, 0x19, 0x58,
+	0x50, 0xcb, 0x97, 0x31, 0x71, 0x71, 0x8c, 0xc6, 0x10, 0xce, 0x18, 0xe2, 0x98, 0xb1, 0x40, 0x9e,
+	0x01, 0x14, 0x50, 0x4e, 0x4e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91,
+	0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5,
+	0x81, 0x14, 0x12, 0xf9, 0x65, 0x45, 0xc9, 0x39, 0xd9, 0xfa, 0x90, 0x42, 0xa4, 0x02, 0x51, 0x16,
+	0x81, 0xc3, 0x23, 0x89, 0x0d, 0x5c, 0x98, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x10, 0xa7,
+	0x90, 0x99, 0xaa, 0x04, 0x00, 0x00,
 }
 
 func (m *MsgCreateProvider) Marshal() (dAtA []byte, err error) {
diff --git a/x/provider/types/types.go b/x/provider/types/types.go
index cfbfb7b0..6e838252 100644
--- a/x/provider/types/types.go
+++ b/x/provider/types/types.go
@@ -5,9 +5,11 @@ import (
 	"fmt"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/ovrclk/akash/types"
 )
 
-type Attributes []sdk.Attribute
+type Attributes []types.Attribute
 
 func (attr Attributes) Validate() error {
 	store := make(map[string]bool)


Test patch:
diff --git a/provider/cluster/inventory_test.go b/provider/cluster/inventory_test.go
index 9c5a7a14..85695696 100644
--- a/provider/cluster/inventory_test.go
+++ b/provider/cluster/inventory_test.go
@@ -3,24 +3,30 @@ package cluster
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+
 	"github.com/ovrclk/akash/types"
 	"github.com/ovrclk/akash/types/unit"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
-	"github.com/stretchr/testify/assert"
 )
 
-const (
-	randCPU    uint32 = 1000
-	randMemory uint64 = 10 * unit.Gi
-)
+func newResourceUnits() types.ResourceUnits {
+	return types.ResourceUnits{
+		CPU:    &types.CPU{Units: types.NewResourceValue(1000)},
+		Memory: &types.Memory{Quantity: types.NewResourceValue(10 * unit.Gi)},
+	}
+}
 
 func TestInventory_reservationAllocateable(t *testing.T) {
-
-	mkrg := func(cpu uint32, memory uint64, count uint32) dtypes.Resource {
+	mkrg := func(cpu uint64, memory uint64, count uint32) dtypes.Resource {
 		return dtypes.Resource{
-			Unit: types.Unit{
-				CPU:    cpu,
-				Memory: memory,
+			Resources: types.ResourceUnits{
+				CPU: &types.CPU{
+					Units: types.NewResourceValue(cpu),
+				},
+				Memory: &types.Memory{
+					Quantity: types.NewResourceValue(memory),
+				},
 			},
 			Count: count,
 		}
@@ -34,8 +40,8 @@ func TestInventory_reservationAllocateable(t *testing.T) {
 	}
 
 	inventory := []Node{
-		NewNode("a", types.Unit{CPU: randCPU, Memory: randMemory}),
-		NewNode("b", types.Unit{CPU: randCPU, Memory: randMemory}),
+		NewNode("a", newResourceUnits()),
+		NewNode("b", newResourceUnits()),
 	}
 
 	reservations := []*reservation{
@@ -51,13 +57,16 @@ func TestInventory_reservationAllocateable(t *testing.T) {
 	}{
 		{mkres(false, mkrg(100, 1*unit.G, 2)), true},
 		{mkres(false, mkrg(100, 4*unit.G, 1)), true},
-		{mkres(false, mkrg(250, 1*unit.G, 1)), true},
+		{mkres(false, mkrg(250, 1*unit.G, 1)), false},
 		{mkres(false, mkrg(1000, 1*unit.G, 1)), false},
 		{mkres(false, mkrg(100, 9*unit.G, 1)), false},
 	}
 
-	for _, test := range tests {
+	assert.Equal(t, tests[0].ok, reservationAllocateable(inventory, reservations, tests[0].res))
+	reservations[0].allocated = true
+	reservations[1].allocated = true
+
+	for _, test := range tests[1:] {
 		assert.Equal(t, test.ok, reservationAllocateable(inventory, reservations, test.res))
 	}
-
 }
diff --git a/sdl/_testdata/profile-svc-name-mismatch.yaml b/sdl/_testdata/profile-svc-name-mismatch.yaml
index 0cf4edb7..681b4dd0 100644
--- a/sdl/_testdata/profile-svc-name-mismatch.yaml
+++ b/sdl/_testdata/profile-svc-name-mismatch.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   webapp:
@@ -13,9 +13,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: "100m"
-      memory: "512Mi"
-      storage: "512Mi"
+      resources:
+        cpu:
+          units: "100m"
+        memory:
+          size: "512Mi"
+        storage:
+          size: "512Mi"
   placement:
     san-jose:
       attributes:
diff --git a/sdl/_testdata/simple.yaml b/sdl/_testdata/simple.yaml
index 78065245..58096451 100644
--- a/sdl/_testdata/simple.yaml
+++ b/sdl/_testdata/simple.yaml
@@ -1,6 +1,5 @@
 ---
-version: "1.5"
-
+version: "2.0"
 services:
   web:
     image: nginx
@@ -8,15 +7,16 @@ services:
       - port: 80
         to:
           - global: true
-
 profiles:
-
   compute:
     web:
-      cpu: "100m"
-      memory: "128Mi"
-      storage: "1Gi"
-
+      resources:
+        cpu:
+          units: "100m"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "1Gi"
   placement:
     westcoast:
       attributes:
@@ -25,7 +25,6 @@ profiles:
         web:
           denom: uakt
           amount: 50
-
 deployment:
   web:
     westcoast:
diff --git a/sdl/coin_test.go b/sdl/coin_test.go
new file mode 100644
index 00000000..8e1797b1
--- /dev/null
+++ b/sdl/coin_test.go
@@ -0,0 +1,39 @@
+package sdl
+
+import (
+	"testing"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/yaml.v3"
+)
+
+func TestPricing(t *testing.T) {
+	tests := []struct {
+		text  string
+		value sdk.Coin
+		err   bool
+	}{
+		{"amount: 1\ndenom: akt", sdk.NewCoin("akt", sdk.NewInt(1)), false},
+		{"amount: -1\ndenom: akt", sdk.NewCoin("akt", sdk.NewInt(1)), true},
+		{"amount: 0.7\ndenom: akt", sdk.NewCoin("akt", sdk.NewInt(1)), true},
+	}
+
+	for idx, test := range tests {
+		buf := []byte(test.text)
+		obj := &v2Coin{}
+
+		err := yaml.Unmarshal(buf, obj)
+
+		if test.err {
+			assert.Error(t, err, "idx:%v text:`%v`", idx, test.text)
+			continue
+		}
+
+		if !assert.NoError(t, err, "idx:%v text:`%v`", idx, test.text) {
+			continue
+		}
+
+		assert.Equal(t, test.value, obj.Value, "idx:%v text:`%v`", idx, test.text)
+	}
+}
diff --git a/sdl/cpu_test.go b/sdl/cpu_test.go
new file mode 100644
index 00000000..2e896f1a
--- /dev/null
+++ b/sdl/cpu_test.go
@@ -0,0 +1,24 @@
+package sdl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+)
+
+func TestV2ResourceCPU_Valid(t *testing.T) {
+	var stream = `
+units: 0.1
+attributes:
+  arch: amd64
+`
+	var p v2ResourceCPU
+
+	err := yaml.Unmarshal([]byte(stream), &p)
+	require.NoError(t, err)
+	require.Equal(t, cpuQuantity(100), p.Units)
+	require.Equal(t, 1, len(p.Attributes))
+	require.Equal(t, "arch", p.Attributes[0].Key)
+	require.Equal(t, "amd64", p.Attributes[0].Value)
+}
diff --git a/sdl/full_test.go b/sdl/full_test.go
new file mode 100644
index 00000000..66712746
--- /dev/null
+++ b/sdl/full_test.go
@@ -0,0 +1,56 @@
+package sdl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+)
+
+func TestFull(t *testing.T) {
+	stream := `
+version: "2.0"
+services:
+  web:
+    image: quay.io/ovrclk/demo-app
+    expose:
+    - port: 80
+      as: 80
+      accept:
+        - hello.localhost
+      to:
+        - global: true
+profiles:
+  compute:
+    web:
+      resources:
+        cpu:
+          units: 0.1
+          attributes:
+            arch: amd64
+        memory:
+          size: 16Mi
+        storage:
+          size: 128Mi
+          attributes:
+            storage-class: ssd
+  placement:
+    westcoast:
+      attributes:
+        region: us-west
+      pricing:
+        web:
+          amount: 1
+          denom: akt
+deployment:
+  web:
+    westcoast:
+      profile: web
+      count: 1
+`
+
+	var sdl v2
+
+	err := yaml.Unmarshal([]byte(stream), &sdl)
+	require.NoError(t, err)
+}
diff --git a/sdl/sdl_test.go b/sdl/sdl_test.go
new file mode 100644
index 00000000..45dcec20
--- /dev/null
+++ b/sdl/sdl_test.go
@@ -0,0 +1,29 @@
+package sdl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+)
+
+func TestSdlVersion(t *testing.T) {
+	stream := `
+version: v2.0
+services:
+  web:
+    image: quay.io/ovrclk/demo-app
+    expose:
+    - port: 80
+      as: 80
+      accept:
+        - hello.localhost
+      to:
+        - global: true
+`
+
+	var sdl sdl
+
+	err := yaml.Unmarshal([]byte(stream), &sdl)
+	require.NoError(t, err)
+}
diff --git a/sdl/units_test.go b/sdl/units_test.go
index d0c5f175..6335570c 100644
--- a/sdl/units_test.go
+++ b/sdl/units_test.go
@@ -3,9 +3,10 @@ package sdl
 import (
 	"testing"
 
-	"github.com/ovrclk/akash/types/unit"
 	"github.com/stretchr/testify/assert"
-	yaml "gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/types/unit"
 )
 
 func TestCPUQuantity(t *testing.T) {
@@ -35,7 +36,7 @@ func TestCPUQuantity(t *testing.T) {
 		buf := []byte(test.text)
 		obj := &vtype{}
 
-		err := yaml.UnmarshalStrict(buf, obj)
+		err := yaml.Unmarshal(buf, obj)
 
 		if test.err {
 			assert.Error(t, err, "idx:%v text:`%v`", idx, test.text)
@@ -82,7 +83,7 @@ func TestByteQuantity(t *testing.T) {
 		buf := []byte(test.text)
 		obj := &vtype{}
 
-		err := yaml.UnmarshalStrict(buf, obj)
+		err := yaml.Unmarshal(buf, obj)
 
 		if test.err {
 			assert.Error(t, err, "idx:%v text:`%v`", idx, test.text)
diff --git a/sdl/v1_test.go b/sdl/v1_test.go
deleted file mode 100644
index 5beedd2a..00000000
--- a/sdl/v1_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package sdl_test
-
-import (
-	"testing"
-
-	sdlv1 "github.com/ovrclk/akash/sdl"
-	"github.com/ovrclk/akash/types"
-	"github.com/ovrclk/akash/types/unit"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-const (
-	randCPU     uint32 = 100
-	randMemory  uint64 = 128 * unit.Mi
-	randStorage uint64 = 1 * unit.Gi
-)
-
-func Test_v1_Parse_Deployments(t *testing.T) {
-	sdl1, err := sdlv1.ReadFile("../x/deployment/testdata/deployment.yaml")
-	require.NoError(t, err)
-	_, err = sdl1.DeploymentGroups()
-	require.NoError(t, err)
-
-	_, err = sdl1.Manifest()
-	require.NoError(t, err)
-
-	sha1, err := sdlv1.Version(sdl1)
-	require.NoError(t, err)
-	assert.Len(t, sha1, 32)
-
-	sha2, err := sdlv1.Version(sdl1)
-	require.NoError(t, err)
-	assert.Len(t, sha2, 32)
-
-	require.Equal(t, sha1, sha2)
-
-	sdl2, err := sdlv1.ReadFile("../x/deployment/testdata/deployment-v2.yaml")
-	require.NoError(t, err)
-	sha3, err := sdlv1.Version(sdl2)
-	require.NoError(t, err)
-	require.NotEqual(t, sha1, sha3)
-}
-
-func Test_v1_Parse_simple(t *testing.T) {
-	sdl, err := sdlv1.ReadFile("./_testdata/simple.yaml")
-	require.NoError(t, err)
-
-	groups, err := sdl.DeploymentGroups()
-	require.NoError(t, err)
-	assert.Len(t, groups, 1)
-
-	group := groups[0]
-	// assert.Len(t, group.GetRequirements(), 1)
-
-	// assert.Equal(t, types.ProviderAttribute{
-	// 	Name:  "region",
-	// 	Value: "us-west",
-	// }, group.GetRequirements()[0])
-
-	assert.Len(t, group.GetResources(), 1)
-
-	assert.Equal(t, types.Resource{
-		Count: 2,
-		Unit: types.Unit{
-			CPU:     randCPU,
-			Memory:  randMemory,
-			Storage: randStorage,
-		},
-	}, group.GetResources()[0])
-
-	mani, err := sdl.Manifest()
-	require.NoError(t, err)
-
-	assert.Len(t, mani.GetGroups(), 1)
-
-	// assert.Equal(t, &types.ManifestGroup{
-	// 	Name: "westcoast",
-	// 	Services: []*types.ManifestService{
-	// 		{
-	// 			Name:  "web",
-	// 			Image: "nginx",
-	// 			Unit: &types.ResourceUnit{
-	// 				CPU:     100,
-	// 				Memory:  128 * unit.Mi,
-	// 				Storage: 1 * unit.Gi,
-	// 			},
-	// 			Count: 2,
-	// 			Expose: []*types.ManifestServiceExpose{
-	// 				{Port: 80, Global: true},
-	// 			},
-	// 		},
-	// 	},
-	// }, mani.GetGroups()[0])
-
-}
-
-func Test_v1_Parse_ProfileNameNotServiceName(t *testing.T) {
-	sdl, err := sdlv1.ReadFile("./_testdata/profile-svc-name-mismatch.yaml")
-	require.NoError(t, err)
-
-	dgroups, err := sdl.DeploymentGroups()
-	require.NoError(t, err)
-	assert.Len(t, dgroups, 1)
-
-	mani, err := sdl.Manifest()
-	require.NoError(t, err)
-	assert.Len(t, mani.GetGroups(), 1)
-}
diff --git a/sdl/v2_test.go b/sdl/v2_test.go
new file mode 100644
index 00000000..15bc69a1
--- /dev/null
+++ b/sdl/v2_test.go
@@ -0,0 +1,138 @@
+package sdl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+
+	"github.com/ovrclk/akash/manifest"
+	atypes "github.com/ovrclk/akash/types"
+	"github.com/ovrclk/akash/types/unit"
+)
+
+func TestV2Expose(t *testing.T) {
+	var stream = `
+- port: 80
+  as: 80
+  accept:
+    - hello.localhost
+  to:
+    - global: true
+`
+
+	var p []v2Expose
+
+	err := yaml.Unmarshal([]byte(stream), &p)
+	require.NoError(t, err)
+}
+
+const (
+	randCPU     uint64 = 100
+	randMemory  uint64 = 128 * unit.Mi
+	randStorage uint64 = 1 * unit.Gi
+)
+
+func TestV2Parse_Deployments(t *testing.T) {
+	sdl1, err := ReadFile("../x/deployment/testdata/deployment.yaml")
+	require.NoError(t, err)
+	_, err = sdl1.DeploymentGroups()
+	require.NoError(t, err)
+
+	_, err = sdl1.Manifest()
+	require.NoError(t, err)
+
+	sha1, err := Version(sdl1)
+	require.NoError(t, err)
+	assert.Len(t, sha1, 32)
+
+	sha2, err := Version(sdl1)
+	require.NoError(t, err)
+	assert.Len(t, sha2, 32)
+
+	require.Equal(t, sha1, sha2)
+
+	sdl2, err := ReadFile("../x/deployment/testdata/deployment-v2.yaml")
+	require.NoError(t, err)
+	sha3, err := Version(sdl2)
+	require.NoError(t, err)
+	require.NotEqual(t, sha1, sha3)
+}
+
+func Test_v1_Parse_simple(t *testing.T) {
+	sdl, err := ReadFile("./_testdata/simple.yaml")
+	require.NoError(t, err)
+
+	groups, err := sdl.DeploymentGroups()
+	require.NoError(t, err)
+	assert.Len(t, groups, 1)
+
+	group := groups[0]
+	assert.Len(t, group.GetResources(), 1)
+
+	// assert.Equal(t, types.Attribute{
+	// 	Name:  "region",
+	// 	Value: "us-west",
+	// }, group.GetRequirements()[0])
+
+	assert.Len(t, group.GetResources(), 1)
+
+	assert.Equal(t, atypes.Resources{
+		Count: 2,
+		Resources: atypes.ResourceUnits{
+			CPU: &atypes.CPU{
+				Units: atypes.NewResourceValue(randCPU),
+			},
+			Memory: &atypes.Memory{
+				Quantity: atypes.NewResourceValue(randMemory),
+			},
+			Storage: &atypes.Storage{
+				Quantity: atypes.NewResourceValue(randStorage),
+			},
+		},
+	}, group.GetResources()[0])
+
+	mani, err := sdl.Manifest()
+	require.NoError(t, err)
+
+	assert.Len(t, mani.GetGroups(), 1)
+
+	assert.Equal(t, manifest.Group{
+		Name: "westcoast",
+		Services: []manifest.Service{
+			{
+				Name:  "web",
+				Image: "nginx",
+				Resources: atypes.ResourceUnits{
+					CPU: &atypes.CPU{
+						Units: atypes.NewResourceValue(100),
+					},
+					Memory: &atypes.Memory{
+						Quantity: atypes.NewResourceValue(128 * unit.Mi),
+					},
+					Storage: &atypes.Storage{
+						Quantity: atypes.NewResourceValue(1 * unit.Gi),
+					},
+				},
+				Count: 2,
+				Expose: []manifest.ServiceExpose{
+					{Port: 80, Global: true},
+				},
+			},
+		},
+	}, mani.GetGroups()[0])
+}
+
+func Test_v1_Parse_ProfileNameNotServiceName(t *testing.T) {
+	sdl, err := ReadFile("./_testdata/profile-svc-name-mismatch.yaml")
+	require.NoError(t, err)
+
+	dgroups, err := sdl.DeploymentGroups()
+	require.NoError(t, err)
+	assert.Len(t, dgroups, 1)
+
+	mani, err := sdl.Manifest()
+	require.NoError(t, err)
+	assert.Len(t, mani.GetGroups(), 1)
+}
diff --git a/testutil/base.go b/testutil/base.go
index 0be4c3fd..cd93f133 100644
--- a/testutil/base.go
+++ b/testutil/base.go
@@ -35,17 +35,17 @@ func Hostname(t testing.TB) string {
 }
 
 // Attribute generates a random sdk.Attribute
-func Attribute(t testing.TB) sdk.Attribute {
+func Attribute(t testing.TB) types.Attribute {
 	t.Helper()
-	return sdk.NewAttribute(Name(t, "attr-key"), Name(t, "attr-value"))
+	return types.NewStringAttribute(Name(t, "attr-key"), Name(t, "attr-value"))
 }
 
 // Attributes generates a set of sdk.Attribute
-func Attributes(t testing.TB) []sdk.Attribute {
+func Attributes(t testing.TB) []types.Attribute {
 	t.Helper()
 	count := rand.Intn(10) + 1
 
-	vals := make([]sdk.Attribute, 0, count)
+	vals := make([]types.Attribute, 0, count)
 	for i := 0; i < count; i++ {
 		vals = append(vals, Attribute(t))
 	}
@@ -63,10 +63,16 @@ func Resources(t testing.TB) []dtypes.Resource {
 	for i := 0; i < count; i++ {
 		coin := sdk.NewCoin(CoinDenom, sdk.NewInt(rand.Int63n(9999)))
 		res := dtypes.Resource{
-			Unit: types.Unit{
-				CPU:     100,
-				Memory:  100,
-				Storage: 10,
+			Resources: types.ResourceUnits{
+				CPU: &types.CPU{
+					Units: types.NewResourceValue(100),
+				},
+				Memory: &types.Memory{
+					Quantity: types.NewResourceValue(1024), // default min value is 1024
+				},
+				Storage: &types.Storage{
+					Quantity: types.NewResourceValue(1024), // default min value is 1024
+				},
 			},
 			Count: 1,
 			Price: coin,
diff --git a/testutil/manifest_app.go b/testutil/manifest_app.go
index 2ca12fd2..ecb77d26 100644
--- a/testutil/manifest_app.go
+++ b/testutil/manifest_app.go
@@ -35,10 +35,16 @@ func (mg manifestGeneratorApp) Service(t testing.TB) manifest.Service {
 	return manifest.Service{
 		Name:  "demo",
 		Image: "ropes/akash-app:v1",
-		Unit: types.Unit{
-			CPU:     100,
-			Memory:  128 * unit.Mi,
-			Storage: 256 * unit.Mi,
+		Resources: types.ResourceUnits{
+			CPU: &types.CPU{
+				Units: types.NewResourceValue(100),
+			},
+			Memory: &types.Memory{
+				Quantity: types.NewResourceValue(128 * unit.Mi),
+			},
+			Storage: &types.Storage{
+				Quantity: types.NewResourceValue(256 * unit.Mi),
+			},
 		},
 		Count: 1,
 		Expose: []manifest.ServiceExpose{
diff --git a/testutil/manifest_overflow.go b/testutil/manifest_overflow.go
index 0b81089d..9ce4d7e7 100644
--- a/testutil/manifest_overflow.go
+++ b/testutil/manifest_overflow.go
@@ -37,10 +37,16 @@ func (mg manifestGeneratorOverflow) Service(t testing.TB) manifest.Service {
 		Image: "quay.io/ovrclk/demo-app",
 		Args:  []string{"run"},
 		Env:   []string{"AKASH_TEST_SERVICE=true"},
-		Unit: types.Unit{
-			CPU:     math.MaxUint32,
-			Memory:  math.MaxUint64,
-			Storage: math.MaxUint64,
+		Resources: types.ResourceUnits{
+			CPU: &types.CPU{
+				Units: types.NewResourceValue(math.MaxUint32),
+			},
+			Memory: &types.Memory{
+				Quantity: types.NewResourceValue(math.MaxUint64),
+			},
+			Storage: &types.Storage{
+				Quantity: types.NewResourceValue(math.MaxUint64),
+			},
 		},
 		Count: math.MaxUint32,
 		Expose: []manifest.ServiceExpose{
diff --git a/testutil/manifest_rand.go b/testutil/manifest_rand.go
index 29fffef7..8e884af6 100644
--- a/testutil/manifest_rand.go
+++ b/testutil/manifest_rand.go
@@ -33,12 +33,12 @@ func (mg manifestGeneratorRand) Group(t testing.TB) manifest.Group {
 func (mg manifestGeneratorRand) Service(t testing.TB) manifest.Service {
 	t.Helper()
 	return manifest.Service{
-		Name:  "demo",
-		Image: "quay.io/ovrclk/demo-app",
-		Args:  []string{"run"},
-		Env:   []string{"AKASH_TEST_SERVICE=true"},
-		Unit:  Unit(t),
-		Count: rand.Uint32(),
+		Name:      "demo",
+		Image:     "quay.io/ovrclk/demo-app",
+		Args:      []string{"run"},
+		Env:       []string{"AKASH_TEST_SERVICE=true"},
+		Resources: ResourceUnits(t),
+		Count:     rand.Uint32(),
 		Expose: []manifest.ServiceExpose{
 			mg.ServiceExpose(t),
 		},
diff --git a/testutil/types.go b/testutil/types.go
index d0362d0f..98d50037 100644
--- a/testutil/types.go
+++ b/testutil/types.go
@@ -21,11 +21,30 @@ import (
 	"github.com/ovrclk/akash/types"
 )
 
-func Unit(_ testing.TB) types.Unit {
-	return types.Unit{
-		CPU:     uint32(rand.Intn(999) + 1),
-		Memory:  uint64(rand.Intn(999) + 1),
-		Storage: uint64(rand.Intn(999) + 1),
+const (
+	defaultMinCPUUnit     = 10
+	defaultMaxCPUUnit     = 500
+	defaultMinMemorySize  = 1024
+	defaultMaxMemorySize  = 1073741824
+	defaultMinStorageSize = 1024
+	defaultMaxStorageSize = 1073741824
+)
+
+func RandRangeInt(min, max int) int {
+	return rand.Intn(max-min) + min
+}
+
+func ResourceUnits(_ testing.TB) types.ResourceUnits {
+	return types.ResourceUnits{
+		CPU: &types.CPU{
+			Units: types.NewResourceValue(uint64(RandRangeInt(defaultMinCPUUnit, defaultMaxCPUUnit))),
+		},
+		Memory: &types.Memory{
+			Quantity: types.NewResourceValue(uint64(RandRangeInt(defaultMinMemorySize, defaultMaxMemorySize))),
+		},
+		Storage: &types.Storage{
+			Quantity: types.NewResourceValue(uint64(RandRangeInt(defaultMinStorageSize, defaultMaxStorageSize))),
+		},
 	}
 }
 
diff --git a/types/resourcevalue_test.go b/types/resourcevalue_test.go
new file mode 100644
index 00000000..13551d68
--- /dev/null
+++ b/types/resourcevalue_test.go
@@ -0,0 +1,24 @@
+package types
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidSum(t *testing.T) {
+	val1 := NewResourceValue(1)
+	val2 := NewResourceValue(1)
+
+	res, err := val1.add(val2)
+	require.NoError(t, err)
+	require.Equal(t, uint64(2), res.Value())
+}
+
+func TestSubToNegative(t *testing.T) {
+	val1 := NewResourceValue(1)
+	val2 := NewResourceValue(2)
+
+	_, err := val1.sub(val2)
+	require.Error(t, err)
+}
diff --git a/validation/manifest_test.go b/validation/manifest_test.go
index 9d3689f7..853542c1 100644
--- a/validation/manifest_test.go
+++ b/validation/manifest_test.go
@@ -3,22 +3,46 @@ package validation_test
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+
 	"github.com/ovrclk/akash/manifest"
 	"github.com/ovrclk/akash/types"
 	"github.com/ovrclk/akash/validation"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
-	"github.com/stretchr/testify/assert"
 )
 
 const (
-	randCPU1    uint32 = 10
-	randCPU2    uint32 = 5
+	randCPU1    uint64 = 10
+	randCPU2    uint64 = 5
 	randMemory  uint64 = 20
 	randStorage uint64 = 5
 )
 
-func Test_ValidateManifest(t *testing.T) {
+var randUnits1 = types.ResourceUnits{
+	CPU: &types.CPU{
+		Units: types.NewResourceValue(randCPU1),
+	},
+	Memory: &types.Memory{
+		Quantity: types.NewResourceValue(randMemory),
+	},
+	Storage: &types.Storage{
+		Quantity: types.NewResourceValue(randStorage),
+	},
+}
 
+var randUnits2 = types.ResourceUnits{
+	CPU: &types.CPU{
+		Units: types.NewResourceValue(randCPU2),
+	},
+	Memory: &types.Memory{
+		Quantity: types.NewResourceValue(randMemory),
+	},
+	Storage: &types.Storage{
+		Quantity: types.NewResourceValue(randStorage),
+	},
+}
+
+func Test_ValidateManifest(t *testing.T) {
 	tests := []struct {
 		name    string
 		ok      bool
@@ -38,13 +62,9 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Name:      "svc1",
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -54,12 +74,8 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Resources: []dtypes.Resource{
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -74,22 +90,14 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 1,
+							Name:      "svc1",
+							Resources: randUnits1,
+							Count:     1,
 						},
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 2,
+							Name:      "svc1",
+							Resources: randUnits1,
+							Count:     2,
 						},
 					},
 				},
@@ -99,12 +107,8 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Resources: []dtypes.Resource{
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -119,13 +123,9 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Name:      "svc1",
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -135,20 +135,12 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Resources: []dtypes.Resource{
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 2,
+							Resources: randUnits1,
+							Count:     2,
 						},
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 1,
+							Resources: randUnits1,
+							Count:     1,
 						},
 					},
 				},
@@ -163,13 +155,9 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo-bad",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Name:      "svc1",
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -179,12 +167,8 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Resources: []dtypes.Resource{
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -199,13 +183,9 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU2,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Name:      "svc1",
+							Resources: randUnits2,
+							Count:     3,
 						},
 					},
 				},
@@ -215,12 +195,8 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Resources: []dtypes.Resource{
 						{
-							Unit: types.Unit{
-								CPU:     randCPU1,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Resources: randUnits1,
+							Count:     3,
 						},
 					},
 				},
@@ -235,13 +211,9 @@ func Test_ValidateManifest(t *testing.T) {
 					Name: "foo",
 					Services: []manifest.Service{
 						{
-							Name: "svc1",
-							Unit: types.Unit{
-								CPU:     randCPU2,
-								Memory:  randMemory,
-								Storage: randStorage,
-							},
-							Count: 3,
+							Name:      "svc1",
+							Resources: randUnits2,
+							Count:     3,
 						},
 					},
 				},
@@ -259,5 +231,4 @@ func Test_ValidateManifest(t *testing.T) {
 			assert.Error(t, err, test.name)
 		}
 	}
-
 }
diff --git a/x/deployment/handler/handler_test.go b/x/deployment/handler/handler_test.go
index 4bff9ce7..682ffac5 100644
--- a/x/deployment/handler/handler_test.go
+++ b/x/deployment/handler/handler_test.go
@@ -259,9 +259,9 @@ func (st *testSuite) createDeployment() (types.Deployment, []types.Group) {
 	group := testutil.DeploymentGroup(st.t, deployment.ID(), 0)
 	group.GroupSpec.Resources = []types.Resource{
 		{
-			Unit:  testutil.Unit(st.t),
-			Count: 1,
-			Price: testutil.Coin(st.t),
+			Resources: testutil.ResourceUnits(st.t),
+			Count:     1,
+			Price:     testutil.Coin(st.t),
 		},
 	}
 	groups := []types.Group{
diff --git a/x/deployment/keeper/grpc_query_test.go b/x/deployment/keeper/grpc_query_test.go
index ed4b964c..5e6ac47f 100644
--- a/x/deployment/keeper/grpc_query_test.go
+++ b/x/deployment/keeper/grpc_query_test.go
@@ -266,9 +266,9 @@ func (suite *grpcTestSuite) createDeployment() (types.Deployment, []types.Group)
 	group := testutil.DeploymentGroup(suite.t, deployment.ID(), 0)
 	group.GroupSpec.Resources = []types.Resource{
 		{
-			Unit:  testutil.Unit(suite.t),
-			Count: 1,
-			Price: testutil.Coin(suite.t),
+			Resources: testutil.ResourceUnits(suite.t),
+			Count:     1,
+			Price:     testutil.Coin(suite.t),
 		},
 	}
 	groups := []types.Group{
diff --git a/x/deployment/testdata/deployment-v2.yaml b/x/deployment/testdata/deployment-v2.yaml
index 26639418..5b7bc90f 100644
--- a/x/deployment/testdata/deployment-v2.yaml
+++ b/x/deployment/testdata/deployment-v2.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   web:
@@ -14,9 +14,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: "0.01"
-      memory: "128Mi"
-      storage: "512Mi"
+      resources:
+        cpu:
+          units: "0.01"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "512Mi"
 
   placement:
     global:
diff --git a/x/deployment/testdata/deployment.yaml b/x/deployment/testdata/deployment.yaml
index a37a3f33..f653f4c6 100644
--- a/x/deployment/testdata/deployment.yaml
+++ b/x/deployment/testdata/deployment.yaml
@@ -1,5 +1,5 @@
 ---
-version: "1.5"
+version: "2.0"
 
 services:
   web:
@@ -14,9 +14,13 @@ services:
 profiles:
   compute:
     web:
-      cpu: "0.01"
-      memory: "128Mi"
-      storage: "512Mi"
+      resources:
+        cpu:
+          units: "0.01"
+        memory:
+          size: "128Mi"
+        storage:
+          size: "512Mi"
 
   placement:
     global:
diff --git a/x/market/client/cli/cli_test.go b/x/market/client/cli/cli_test.go
index 1e1da059..a50bc427 100644
--- a/x/market/client/cli/cli_test.go
+++ b/x/market/client/cli/cli_test.go
@@ -196,7 +196,7 @@ func (s *IntegrationTestSuite) Test2CreateBid() {
 	resp, err = cli.QueryOrdersExec(val.ClientCtx.WithOutputFormat("json"))
 	s.Require().NoError(err)
 
-	var result *types.QueryOrdersResponse = &types.QueryOrdersResponse{}
+	result := &types.QueryOrdersResponse{}
 	err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp.Bytes(), result)
 	s.Require().NoError(err)
 	s.Require().Len(result.Orders, 1)
diff --git a/x/market/handler/handler_test.go b/x/market/handler/handler_test.go
index cccbf46f..1152e703 100644
--- a/x/market/handler/handler_test.go
+++ b/x/market/handler/handler_test.go
@@ -17,6 +17,7 @@ import (
 	dbm "github.com/tendermint/tm-db"
 
 	"github.com/ovrclk/akash/testutil"
+	atypes "github.com/ovrclk/akash/types"
 	dkeeper "github.com/ovrclk/akash/x/deployment/keeper"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
 	"github.com/ovrclk/akash/x/market/handler"
@@ -448,7 +449,7 @@ func (st *testSuite) createOrder(resources []dtypes.Resource) (types.Order, dtyp
 	return order, group.GroupSpec
 }
 
-func (st *testSuite) createProvider(attr []sdk.Attribute) ptypes.Provider {
+func (st *testSuite) createProvider(attr []atypes.Attribute) ptypes.Provider {
 	st.t.Helper()
 
 	prov := ptypes.Provider{
diff --git a/x/provider/types/msgs_test.go b/x/provider/types/msgs_test.go
index 8e633cc3..e024329d 100644
--- a/x/provider/types/msgs_test.go
+++ b/x/provider/types/msgs_test.go
@@ -8,6 +8,8 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 	"github.com/pkg/errors"
+
+	"github.com/ovrclk/akash/types"
 )
 
 func TestConfigPath(t *testing.T) {
@@ -189,7 +191,7 @@ var msgCreateTests = []providerTestParams{
 		msg: Provider{
 			Owner:   sdk.AccAddress("hihi"),
 			HostURI: "http://localhost:3001",
-			Attributes: []sdk.Attribute{
+			Attributes: []types.Attribute{
 				{
 					Key:   "hihi",
 					Value: "neh",
@@ -202,7 +204,7 @@ var msgCreateTests = []providerTestParams{
 		msg: Provider{
 			Owner:   sdk.AccAddress(""),
 			HostURI: "http://localhost:3001",
-			Attributes: []sdk.Attribute{
+			Attributes: []types.Attribute{
 				{
 					Key:   "hihi",
 					Value: "neh",
@@ -216,7 +218,7 @@ var msgCreateTests = []providerTestParams{
 		msg: Provider{
 			Owner:   sdk.AccAddress("hihi"),
 			HostURI: "ht tp://foo.com",
-			Attributes: []sdk.Attribute{
+			Attributes: []types.Attribute{
 				{
 					Key:   "hihi",
 					Value: "neh",
@@ -229,7 +231,7 @@ var msgCreateTests = []providerTestParams{
 		msg: Provider{
 			Owner:   sdk.AccAddress("hihi"),
 			HostURI: "",
-			Attributes: []sdk.Attribute{
+			Attributes: []types.Attribute{
 				{
 					Key:   "hihi",
 					Value: "neh",