Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit 8b75520

Browse files
authored
Token v0 foundation (#101)
token V.0 foundation, state interface refactoring
1 parent 0e23846 commit 8b75520

55 files changed

Lines changed: 15092 additions & 108 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# ERC-20 token with fixed `total supply`
2+
3+
With:
4+
5+
* [addresses](../../service/account)
6+
* [config for 1 token per chaincode](../../service/config_erc20)
7+
* [balances and transfers between balances](../../service/balance)
8+
* [allowance and transfers by spender from owner account](../../service/allowance)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package erc20
2+
3+
import (
4+
"errors"
5+
6+
"github.com/s7techlab/cckit/examples/token/service/account"
7+
"github.com/s7techlab/cckit/examples/token/service/allowance"
8+
"github.com/s7techlab/cckit/examples/token/service/balance"
9+
"github.com/s7techlab/cckit/examples/token/service/config"
10+
"github.com/s7techlab/cckit/examples/token/service/config_erc20"
11+
"github.com/s7techlab/cckit/router"
12+
)
13+
14+
var (
15+
Token = &config.CreateTokenTypeRequest{
16+
Name: `SomeToken`,
17+
Symbol: `@`,
18+
Decimals: 2,
19+
TotalSupply: 10000000,
20+
}
21+
)
22+
23+
func New() (*router.Chaincode, error) {
24+
r := router.New(`erc20`)
25+
26+
// accountSvc resolves address as base58( invoker.Cert.PublicKey )
27+
accountSvc := account.NewLocalService()
28+
configSvc := config.NewStateService()
29+
// Balance management service
30+
balanceSvc := balance.New(accountSvc, configSvc)
31+
// Allowance management service
32+
allowanceSvc := allowance.NewService(balanceSvc)
33+
34+
erc20ConfigSvc := &config_erc20.ERC20Service{Token: configSvc}
35+
36+
r.Init(func(ctx router.Context) (interface{}, error) {
37+
// add token definition to state if not exists
38+
token, err := config.CreateDefaultToken(ctx, configSvc, Token)
39+
if err != nil {
40+
if errors.Is(err, config.ErrTokenAlreadyExists) {
41+
return nil, nil
42+
}
43+
return nil, err
44+
}
45+
46+
// get chaincode instantiator address
47+
ownerAddress, err := accountSvc.GetInvokerAddress(ctx, nil)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
// add `TotalSupply` to chaincode first committer
53+
if err = balance.NewStore(ctx).Add(ownerAddress.Address, token, Token.TotalSupply); err != nil {
54+
return nil, err
55+
}
56+
57+
return nil, nil
58+
})
59+
60+
if err := balance.RegisterBalanceServiceChaincode(r, balanceSvc); err != nil {
61+
return nil, err
62+
}
63+
if err := account.RegisterAccountServiceChaincode(r, accountSvc); err != nil {
64+
return nil, err
65+
}
66+
if err := config_erc20.RegisterConfigERC20ServiceChaincode(r, erc20ConfigSvc); err != nil {
67+
return nil, err
68+
}
69+
if err := allowance.RegisterAllowanceServiceChaincode(r, allowanceSvc); err != nil {
70+
return nil, err
71+
}
72+
73+
return router.NewChaincode(r), nil
74+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package erc20_test
2+
3+
import (
4+
"encoding/base64"
5+
"testing"
6+
7+
. "github.com/onsi/ginkgo"
8+
. "github.com/onsi/gomega"
9+
10+
"github.com/s7techlab/cckit/examples/token/chaincode/erc20"
11+
"github.com/s7techlab/cckit/examples/token/service/account"
12+
"github.com/s7techlab/cckit/examples/token/service/allowance"
13+
"github.com/s7techlab/cckit/examples/token/service/balance"
14+
"github.com/s7techlab/cckit/examples/token/service/config_erc20"
15+
"github.com/s7techlab/cckit/identity"
16+
"github.com/s7techlab/cckit/identity/testdata"
17+
testcc "github.com/s7techlab/cckit/testing"
18+
expectcc "github.com/s7techlab/cckit/testing/expect"
19+
)
20+
21+
func TestERC20(t *testing.T) {
22+
RegisterFailHandler(Fail)
23+
RunSpecs(t, "ERC20 Test suite")
24+
}
25+
26+
var (
27+
ownerIdentity = testdata.Certificates[0].MustIdentity(testdata.DefaultMSP)
28+
user1Identity = testdata.Certificates[1].MustIdentity(testdata.DefaultMSP)
29+
user2Identity = testdata.Certificates[2].MustIdentity(testdata.DefaultMSP)
30+
31+
ownerAddress = base64.StdEncoding.EncodeToString(identity.MarshalPublicKey(ownerIdentity.Cert.PublicKey))
32+
user1Address = base64.StdEncoding.EncodeToString(identity.MarshalPublicKey(user1Identity.Cert.PublicKey))
33+
user2Address = base64.StdEncoding.EncodeToString(identity.MarshalPublicKey(user2Identity.Cert.PublicKey))
34+
35+
cc *testcc.MockStub
36+
)
37+
38+
var _ = Describe(`ERC`, func() {
39+
40+
BeforeSuite(func() {
41+
chaincode, err := erc20.New()
42+
Expect(err).NotTo(HaveOccurred())
43+
cc = testcc.NewMockStub(`erc20`, chaincode)
44+
45+
expectcc.ResponseOk(cc.From(ownerIdentity).Init())
46+
})
47+
48+
It(`Allow to call init once more time `, func() {
49+
expectcc.ResponseOk(cc.From(ownerIdentity).Init())
50+
})
51+
52+
Context(`token info`, func() {
53+
54+
It(`Allow to get token name`, func() {
55+
name := expectcc.PayloadIs(
56+
cc.From(user1Identity).
57+
Query(config_erc20.ConfigERC20ServiceChaincode_GetName, nil),
58+
&config_erc20.NameResponse{}).(*config_erc20.NameResponse)
59+
60+
Expect(name.Name).To(Equal(erc20.Token.Name))
61+
})
62+
})
63+
64+
Context(`initial balance`, func() {
65+
66+
It(`Allow to know invoker address `, func() {
67+
address := expectcc.PayloadIs(
68+
cc.From(user1Identity).
69+
Query(account.AccountServiceChaincode_GetInvokerAddress, nil),
70+
&account.AddressId{}).(*account.AddressId)
71+
72+
Expect(address.Address).To(Equal(user1Address))
73+
74+
address = expectcc.PayloadIs(
75+
cc.From(user2Identity).
76+
Query(account.AccountServiceChaincode_GetInvokerAddress, nil),
77+
&account.AddressId{}).(*account.AddressId)
78+
79+
Expect(address.Address).To(Equal(user2Address))
80+
})
81+
82+
It(`Allow to get owner balance`, func() {
83+
b := expectcc.PayloadIs(
84+
cc.From(user1Identity). // call by any user
85+
Query(balance.BalanceServiceChaincode_GetBalance,
86+
&balance.BalanceId{Address: ownerAddress, Token: []string{erc20.Token.Name}}),
87+
&balance.Balance{}).(*balance.Balance)
88+
89+
Expect(b.Address).To(Equal(ownerAddress))
90+
Expect(b.Amount).To(Equal(uint64(erc20.Token.TotalSupply)))
91+
})
92+
93+
It(`Allow to get zero balance`, func() {
94+
b := expectcc.PayloadIs(
95+
cc.From(user1Identity).
96+
Query(balance.BalanceServiceChaincode_GetBalance,
97+
&balance.BalanceId{Address: user1Address, Token: []string{erc20.Token.Name}}),
98+
&balance.Balance{}).(*balance.Balance)
99+
100+
Expect(b.Amount).To(Equal(uint64(0)))
101+
})
102+
103+
})
104+
105+
Context(`transfer`, func() {
106+
var transferAmount uint64 = 100
107+
108+
It(`Disallow to transfer balance by user with zero balance`, func() {
109+
expectcc.ResponseError(
110+
cc.From(user1Identity).
111+
Invoke(balance.BalanceServiceChaincode_Transfer,
112+
&balance.TransferRequest{
113+
RecipientAddress: user2Address,
114+
Token: []string{erc20.Token.Name},
115+
Amount: transferAmount,
116+
}), balance.ErrAmountInsuficcient)
117+
118+
})
119+
120+
It(`Allow to transfer balance by owner`, func() {
121+
r := expectcc.PayloadIs(
122+
cc.From(ownerIdentity).
123+
Invoke(balance.BalanceServiceChaincode_Transfer,
124+
&balance.TransferRequest{
125+
RecipientAddress: user1Address,
126+
Token: []string{erc20.Token.Name},
127+
Amount: transferAmount,
128+
}),
129+
&balance.TransferResponse{}).(*balance.TransferResponse)
130+
131+
Expect(r.SenderAddress).To(Equal(ownerAddress))
132+
Expect(r.Amount).To(Equal(transferAmount))
133+
})
134+
135+
It(`Allow to get new non zero balance`, func() {
136+
b := expectcc.PayloadIs(
137+
cc.From(user1Identity).
138+
Query(balance.BalanceServiceChaincode_GetBalance,
139+
&balance.BalanceId{Address: user1Address, Token: []string{erc20.Token.Name}}),
140+
&balance.Balance{}).(*balance.Balance)
141+
142+
Expect(b.Amount).To(Equal(transferAmount))
143+
})
144+
145+
})
146+
147+
Context(`Allowance`, func() {
148+
149+
var allowAmount uint64 = 50
150+
151+
It(`Allow to approve amount by owner for spender even if balance is zero`, func() {
152+
a := expectcc.PayloadIs(
153+
cc.From(user2Identity).
154+
Invoke(allowance.AllowanceServiceChaincode_Approve,
155+
&allowance.ApproveRequest{
156+
OwnerAddress: user2Address,
157+
SpenderAddress: user1Address,
158+
Token: []string{erc20.Token.Name},
159+
Amount: allowAmount,
160+
}),
161+
&allowance.Allowance{}).(*allowance.Allowance)
162+
163+
Expect(a.OwnerAddress).To(Equal(user2Address))
164+
Expect(a.SpenderAddress).To(Equal(user1Address))
165+
Expect(a.Amount).To(Equal(allowAmount))
166+
})
167+
It(`Disallow to approve amount by non owner`, func() {
168+
expectcc.ResponseError(
169+
cc.From(user2Identity).
170+
Invoke(allowance.AllowanceServiceChaincode_Approve,
171+
&allowance.ApproveRequest{
172+
OwnerAddress: ownerAddress,
173+
SpenderAddress: user1Address,
174+
Token: []string{erc20.Token.Name},
175+
Amount: allowAmount,
176+
}), allowance.ErrOwnerOnly)
177+
})
178+
179+
It(`Allow to approve amount by owner for spender if amount is sufficient`, func() {
180+
a := expectcc.PayloadIs(
181+
cc.From(ownerIdentity).
182+
Invoke(allowance.AllowanceServiceChaincode_Approve,
183+
&allowance.ApproveRequest{
184+
OwnerAddress: ownerAddress,
185+
SpenderAddress: user2Address,
186+
Token: []string{erc20.Token.Name},
187+
Amount: allowAmount,
188+
}),
189+
&allowance.Allowance{}).(*allowance.Allowance)
190+
191+
Expect(a.OwnerAddress).To(Equal(ownerAddress))
192+
Expect(a.SpenderAddress).To(Equal(user2Address))
193+
Expect(a.Amount).To(Equal(allowAmount))
194+
})
195+
196+
It(`Allow to transfer from`, func() {
197+
spenderIdentity := user2Identity
198+
spenderAddress := user2Address
199+
200+
t := expectcc.PayloadIs(
201+
cc.From(spenderIdentity).
202+
Invoke(allowance.AllowanceServiceChaincode_TransferFrom,
203+
&allowance.TransferFromRequest{
204+
OwnerAddress: ownerAddress,
205+
RecipientAddress: spenderAddress,
206+
Token: []string{erc20.Token.Name},
207+
Amount: allowAmount,
208+
}),
209+
&allowance.TransferFromResponse{}).(*allowance.TransferFromResponse)
210+
211+
Expect(t.OwnerAddress).To(Equal(ownerAddress))
212+
Expect(t.RecipientAddress).To(Equal(spenderAddress))
213+
Expect(t.Amount).To(Equal(allowAmount))
214+
})
215+
})
216+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package account
2+
3+
import (
4+
"encoding/base64"
5+
"errors"
6+
7+
"google.golang.org/protobuf/types/known/emptypb"
8+
9+
"github.com/s7techlab/cckit/identity"
10+
"github.com/s7techlab/cckit/router"
11+
)
12+
13+
type LocalService struct {
14+
}
15+
16+
func NewLocalService() *LocalService {
17+
return &LocalService{}
18+
}
19+
20+
func (l *LocalService) GetInvokerAddress(ctx router.Context, _ *emptypb.Empty) (*AddressId, error) {
21+
invoker, err := identity.FromStub(ctx.Stub())
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
return l.GetAddress(ctx,
27+
&GetAddressRequest{PublicKey: identity.MarshalPublicKey(invoker.Cert.PublicKey)})
28+
}
29+
30+
func (l *LocalService) GetAddress(ctx router.Context, req *GetAddressRequest) (*AddressId, error) {
31+
return &AddressId{
32+
Address: base64.StdEncoding.EncodeToString(req.PublicKey),
33+
}, nil
34+
}
35+
36+
func (l *LocalService) GetAccount(ctx router.Context, id *AccountId) (*Account, error) {
37+
return nil, errors.New(`no accounts implemented`)
38+
}
39+
40+
type Remote struct {
41+
}

0 commit comments

Comments
 (0)