Skip to content

Commit 7fac7fa

Browse files
authored
Default Contract Manager Implementation (#1687)
1 parent 0459bd4 commit 7fac7fa

File tree

8 files changed

+244
-192
lines changed

8 files changed

+244
-192
lines changed

x/contracts/runtime/call_context_test.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/stretchr/testify/require"
1414

1515
"github.com/ava-labs/hypersdk/codec"
16+
"github.com/ava-labs/hypersdk/x/contracts/test"
1617
)
1718

1819
func TestCallContext(t *testing.T) {
@@ -22,11 +23,14 @@ func TestCallContext(t *testing.T) {
2223
contractID := ids.GenerateTestID()
2324
contractAccount := codec.CreateAddress(0, contractID)
2425
stringedID := string(contractID[:])
26+
contractManager := NewContractStateManager(test.NewTestDB(), []byte{})
27+
err := contractManager.SetAccountContract(ctx, contractAccount, ContractID(stringedID))
28+
require.NoError(err)
2529
testStateManager := &TestStateManager{
26-
ContractsMap: map[string][]byte{},
27-
AccountMap: map[codec.Address]string{contractAccount: stringedID},
30+
ContractManager: contractManager,
2831
}
29-
err := testStateManager.CompileAndSetContract(ContractID(stringedID), "call_contract")
32+
33+
err = testStateManager.CompileAndSetContract(ContractID(stringedID), "call_contract")
3034
require.NoError(err)
3135

3236
r := NewRuntime(
@@ -75,12 +79,13 @@ func TestCallContextPreventOverwrite(t *testing.T) {
7579
contract1Address := codec.CreateAddress(1, contract1ID)
7680
stringedID0 := string(contract0ID[:])
7781

82+
contractManager := NewContractStateManager(test.NewTestDB(), []byte{})
83+
err := contractManager.SetAccountContract(ctx, contract0Address, ContractID(stringedID0))
84+
require.NoError(err)
7885
testStateManager := &TestStateManager{
79-
ContractsMap: map[string][]byte{},
80-
AccountMap: map[codec.Address]string{contract0Address: stringedID0},
86+
ContractManager: contractManager,
8187
}
82-
83-
err := testStateManager.CompileAndSetContract(ContractID(stringedID0), "call_contract")
88+
err = testStateManager.CompileAndSetContract(ContractID(stringedID0), "call_contract")
8489
require.NoError(err)
8590

8691
r := NewRuntime(
@@ -94,10 +99,13 @@ func TestCallContextPreventOverwrite(t *testing.T) {
9499
})
95100

96101
stringedID1 := string(contract1ID[:])
102+
contractManager1 := NewContractStateManager(test.NewTestDB(), []byte{})
103+
err = contractManager.SetAccountContract(ctx, contract1Address, ContractID(stringedID1))
104+
require.NoError(err)
97105
testStateManager1 := &TestStateManager{
98-
ContractsMap: map[string][]byte{},
99-
AccountMap: map[codec.Address]string{contract1Address: stringedID1},
106+
ContractManager: contractManager1,
100107
}
108+
101109
err = testStateManager1.CompileAndSetContract(ContractID(stringedID1), "call_contract")
102110
require.NoError(err)
103111

x/contracts/runtime/manager.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package runtime
5+
6+
import (
7+
"context"
8+
"crypto/sha256"
9+
"errors"
10+
11+
"github.com/ava-labs/avalanchego/database"
12+
"github.com/ava-labs/avalanchego/ids"
13+
14+
"github.com/ava-labs/hypersdk/codec"
15+
"github.com/ava-labs/hypersdk/state"
16+
)
17+
18+
var (
19+
_ ContractManager = &ContractStateManager{}
20+
ErrUnknownAccount = errors.New("unknown account")
21+
contractKeyBytes = []byte("contract")
22+
)
23+
24+
const (
25+
// Global directory of contractIDs to contractBytes
26+
contractPrefix = 0x0
27+
28+
// Prefix for all contract state spaces
29+
accountPrefix = 0x1
30+
// Associated data for an account, such as the contractID
31+
accountDataPrefix = 0x0
32+
// State space associated with an account
33+
accountStatePrefix = 0x1
34+
)
35+
36+
// ContractStateManager is an out of the box implementation of the ContractManager interface.
37+
// The contract state manager is responsible for managing all state keys associated with contracts.
38+
type ContractStateManager struct {
39+
db state.Mutable
40+
}
41+
42+
// NewContractStateManager returns a new ContractStateManager instance.
43+
// [prefix] must be unique to ensures the contract's state space
44+
// remains isolated from other state spaces in [db].
45+
func NewContractStateManager(
46+
db state.Mutable,
47+
prefix []byte,
48+
) *ContractStateManager {
49+
prefixedState := newPrefixStateMutable(prefix, db)
50+
51+
return &ContractStateManager{
52+
db: prefixedState,
53+
}
54+
}
55+
56+
// GetContractState returns a mutable state instance associated with [account].
57+
func (p *ContractStateManager) GetContractState(account codec.Address) state.Mutable {
58+
return newAccountPrefixedMutable(account, p.db)
59+
}
60+
61+
// GetAccountContract grabs the associated id with [account]. The ID is the key mapping to the contractbytes
62+
// Errors if there is no found account or an error fetching
63+
func (p *ContractStateManager) GetAccountContract(ctx context.Context, account codec.Address) (ContractID, error) {
64+
contractID, exists, err := p.getAccountContract(ctx, account)
65+
if err != nil {
66+
return ids.Empty[:], err
67+
}
68+
if !exists {
69+
return ids.Empty[:], ErrUnknownAccount
70+
}
71+
return contractID[:], nil
72+
}
73+
74+
// [contractID] -> [contractBytes]
75+
func (p *ContractStateManager) GetContractBytes(ctx context.Context, contractID ContractID) ([]byte, error) {
76+
// TODO: take fee out of balance?
77+
contractBytes, err := p.db.GetValue(ctx, contractKey(contractID))
78+
if err != nil {
79+
return []byte{}, ErrUnknownAccount
80+
}
81+
82+
return contractBytes, nil
83+
}
84+
85+
func (p *ContractStateManager) NewAccountWithContract(ctx context.Context, contractID ContractID, accountCreationData []byte) (codec.Address, error) {
86+
newID := sha256.Sum256(append(contractID, accountCreationData...))
87+
newAccount := codec.CreateAddress(0, newID)
88+
return newAccount, p.SetAccountContract(ctx, newAccount, contractID)
89+
}
90+
91+
func (p *ContractStateManager) SetAccountContract(ctx context.Context, account codec.Address, contractID ContractID) error {
92+
return p.db.Insert(ctx, accountDataKey(account[:], contractKeyBytes), contractID)
93+
}
94+
95+
// setContract stores [contract] at [contractID]
96+
func (p *ContractStateManager) SetContractBytes(
97+
ctx context.Context,
98+
contractID ContractID,
99+
contract []byte,
100+
) error {
101+
return p.db.Insert(ctx, contractKey(contractID[:]), contract)
102+
}
103+
104+
func contractKey(key []byte) (k []byte) {
105+
k = make([]byte, 0, 1+len(key))
106+
k = append(k, contractPrefix)
107+
k = append(k, key...)
108+
return
109+
}
110+
111+
// Creates a key an account balance key
112+
func accountDataKey(account []byte, key []byte) (k []byte) {
113+
// accountPrefix + account + accountDataPrefix + key
114+
k = make([]byte, 0, 2+len(account)+len(key))
115+
k = append(k, accountPrefix)
116+
k = append(k, account...)
117+
k = append(k, accountDataPrefix)
118+
k = append(k, key...)
119+
return
120+
}
121+
122+
func accountContractKey(account []byte) []byte {
123+
return accountDataKey(account, contractKeyBytes)
124+
}
125+
126+
func (p *ContractStateManager) getAccountContract(ctx context.Context, account codec.Address) (ids.ID, bool, error) {
127+
v, err := p.db.GetValue(ctx, accountContractKey(account[:]))
128+
if errors.Is(err, database.ErrNotFound) {
129+
return ids.Empty, false, nil
130+
}
131+
if err != nil {
132+
return ids.Empty, false, err
133+
}
134+
return ids.ID(v[:ids.IDLen]), true, nil
135+
}
136+
137+
// prefixed state
138+
type prefixedStateMutable struct {
139+
inner state.Mutable
140+
prefix []byte
141+
}
142+
143+
func newPrefixStateMutable(prefix []byte, inner state.Mutable) *prefixedStateMutable {
144+
return &prefixedStateMutable{inner: inner, prefix: prefix}
145+
}
146+
147+
func (s *prefixedStateMutable) prefixKey(key []byte) (k []byte) {
148+
k = make([]byte, len(s.prefix)+len(key))
149+
copy(k, s.prefix)
150+
copy(k[len(s.prefix):], key)
151+
return
152+
}
153+
154+
func (s *prefixedStateMutable) GetValue(ctx context.Context, key []byte) (value []byte, err error) {
155+
return s.inner.GetValue(ctx, s.prefixKey(key))
156+
}
157+
158+
func (s *prefixedStateMutable) Insert(ctx context.Context, key []byte, value []byte) error {
159+
return s.inner.Insert(ctx, s.prefixKey(key), value)
160+
}
161+
162+
func (s *prefixedStateMutable) Remove(ctx context.Context, key []byte) error {
163+
return s.inner.Remove(ctx, s.prefixKey(key))
164+
}
165+
166+
func newAccountPrefixedMutable(account codec.Address, mutable state.Mutable) state.Mutable {
167+
return &prefixedStateMutable{inner: mutable, prefix: accountStateKey(account[:])}
168+
}
169+
170+
// [accountPrefix] + [account] + [accountStatePrefix] = state space associated with a contract
171+
func accountStateKey(key []byte) (k []byte) {
172+
k = make([]byte, 2+len(key))
173+
k[0] = accountPrefix
174+
copy(k[1:], key)
175+
k[len(k)-1] = accountStatePrefix
176+
return
177+
}

x/contracts/runtime/runtime.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type ContractManager interface {
5050
NewAccountWithContract(ctx context.Context, contractID ContractID, accountCreationData []byte) (codec.Address, error)
5151
// SetAccountContract associates the given contract ID with the given account.
5252
SetAccountContract(ctx context.Context, account codec.Address, contractID ContractID) error
53+
// SetContractBytes stores the compiled WASM bytes of the contract with the given ID.
54+
SetContractBytes(ctx context.Context, contractID ContractID, contractBytes []byte) error
5355
}
5456

5557
func NewRuntime(

x/contracts/runtime/util_test.go

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,16 @@ import (
1919
)
2020

2121
type TestStateManager struct {
22-
ContractsMap map[string][]byte
23-
AccountMap map[codec.Address]string
24-
Balances map[codec.Address]uint64
25-
Mu state.Mutable
22+
ContractManager *ContractStateManager
23+
Balances map[codec.Address]uint64
2624
}
2725

28-
func (t TestStateManager) GetAccountContract(_ context.Context, account codec.Address) (ContractID, error) {
29-
if contractID, ok := t.AccountMap[account]; ok {
30-
return ContractID(contractID), nil
31-
}
32-
return ids.Empty[:], nil
26+
func (t TestStateManager) GetAccountContract(ctx context.Context, account codec.Address) (ContractID, error) {
27+
return t.ContractManager.GetAccountContract(ctx, account)
3328
}
3429

35-
func (t TestStateManager) GetContractBytes(_ context.Context, contractID ContractID) ([]byte, error) {
36-
contractBytes, ok := t.ContractsMap[string(contractID)]
37-
if !ok {
38-
return nil, errors.New("couldn't find contract")
39-
}
40-
41-
return contractBytes, nil
30+
func (t TestStateManager) GetContractBytes(ctx context.Context, contractID ContractID) ([]byte, error) {
31+
return t.ContractManager.GetContractBytes(ctx, contractID)
4232
}
4333

4434
func compileContract(contractName string) ([]byte, error) {
@@ -59,28 +49,24 @@ func compileContract(contractName string) ([]byte, error) {
5949
return contractBytes, nil
6050
}
6151

62-
func (t TestStateManager) SetContractBytes(contractID ContractID, contractBytes []byte) {
63-
t.ContractsMap[string(contractID)] = contractBytes
52+
func (t TestStateManager) SetContractBytes(ctx context.Context, contractID ContractID, contractBytes []byte) error {
53+
return t.ContractManager.SetContractBytes(ctx, contractID, contractBytes)
6454
}
6555

6656
func (t TestStateManager) CompileAndSetContract(contractID ContractID, contractName string) error {
6757
contractBytes, err := compileContract(contractName)
6858
if err != nil {
6959
return err
7060
}
71-
t.SetContractBytes(contractID, contractBytes)
72-
return nil
61+
return t.SetContractBytes(context.Background(), contractID, contractBytes)
7362
}
7463

7564
func (t TestStateManager) NewAccountWithContract(_ context.Context, contractID ContractID, _ []byte) (codec.Address, error) {
76-
account := codec.CreateAddress(0, ids.GenerateTestID())
77-
t.AccountMap[account] = string(contractID)
78-
return account, nil
65+
return t.ContractManager.NewAccountWithContract(context.Background(), contractID, []byte{})
7966
}
8067

8168
func (t TestStateManager) SetAccountContract(_ context.Context, account codec.Address, contractID ContractID) error {
82-
t.AccountMap[account] = string(contractID)
83-
return nil
69+
return t.ContractManager.SetAccountContract(context.Background(), account, contractID)
8470
}
8571

8672
func (t TestStateManager) GetBalance(_ context.Context, address codec.Address) (uint64, error) {
@@ -104,7 +90,7 @@ func (t TestStateManager) TransferBalance(ctx context.Context, from codec.Addres
10490
}
10591

10692
func (t TestStateManager) GetContractState(address codec.Address) state.Mutable {
107-
return &prefixedState{address: address, inner: t.Mu}
93+
return t.ContractManager.GetContractState(address)
10894
}
10995

11096
var _ state.Mutable = (*prefixedState)(nil)
@@ -197,9 +183,7 @@ func (t *testRuntime) AddContract(contractID ContractID, account codec.Address,
197183
if err != nil {
198184
return err
199185
}
200-
201-
t.StateManager.(TestStateManager).AccountMap[account] = string(contractID)
202-
return nil
186+
return t.StateManager.(TestStateManager).SetAccountContract(t.Context, account, contractID)
203187
}
204188

205189
func (t *testRuntime) CallContract(contract codec.Address, function string, params ...interface{}) ([]byte, error) {
@@ -220,10 +204,8 @@ func newTestRuntime(ctx context.Context) *testRuntime {
220204
NewConfig(),
221205
logging.NoLog{}).WithDefaults(CallInfo{Fuel: 1000000000}),
222206
StateManager: TestStateManager{
223-
ContractsMap: map[string][]byte{},
224-
AccountMap: map[codec.Address]string{},
225-
Balances: map[codec.Address]uint64{},
226-
Mu: test.NewTestDB(),
207+
ContractManager: NewContractStateManager(test.NewTestDB(), []byte{}),
208+
Balances: map[codec.Address]uint64{},
227209
},
228210
}
229211
}

x/contracts/simulator/ffi/ffi.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ func CreateContract(db *C.Mutable, path *C.char) C.CreateContractResponse {
8787
}
8888
}
8989

90-
contractID, err := generateRandomID()
90+
id, err := generateRandomID()
9191
if err != nil {
9292
return C.CreateContractResponse{
9393
error: C.CString(err.Error()),
9494
}
9595
}
9696

97-
err = contractManager.SetContract(context.TODO(), contractID, contractBytes)
97+
contractID := runtime.ContractID(id[:])
98+
err = contractManager.SetContractBytes(context.TODO(), contractID, contractBytes)
9899
if err != nil {
99100
errmsg := "contract creation failed: " + err.Error()
100101
return C.CreateContractResponse{

0 commit comments

Comments
 (0)