diff --git a/README.md b/README.md index 7313137..db4589c 100644 --- a/README.md +++ b/README.md @@ -288,10 +288,13 @@ Default plan files shipped with pond can be found in `$HOME/.pond/planfiles`. ```json { "denom": "{{ .Denoms.USDC.Path }}", - "address": "{{ .Contracts.my_contract.Address }}" + "address": "{{ .Contracts.my_contract.Address }}", + "code_id": "{{ .CodeIds.my_contract }} | int" } ``` +Note: To be able to use the resulting code id as an integer value, you need to add `| int` after the template string. Otherwise the code id is provided as a string. + All deployments are done from the `deployer` account: `kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse` ### Syntax @@ -328,9 +331,20 @@ The above example will create the `POND` token as `factory/kujira1k3g54c2sc7g9mg It stores the path of all three denoms, which can be accessed in subsequent contract instantiations of that deployment via `{{ .Denoms.POND.Address }}` for example. +#### Codes + +If you are working on contracts that change a lot during the development, updating the registry all the might become a tedious task. Therefore you can specify your sources directly in the plan file and Pond deploys them and updates the registry accordingly. + +```json +{ + "codes": + "my_project": "file:///path/to/my_project/artifacts/my_project-aarch64.wasm" +} +``` + #### Contracts -Pond instantiates all contracts from the `deployer` account, which also is set as the owner by default. +Pond instantiates all contracts from the `deployer` account. To speed up the deployments, Pond handles contracts in batches which combine all instantiations into a single transaction: diff --git a/cmd/init.go b/cmd/init.go index 40f8d27..74ec2af 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -21,6 +21,7 @@ var ( NoContracts bool ApiUrl string RpcUrl string + KujiraVersion string ) // initCmd represents the init command @@ -63,7 +64,7 @@ var initCmd = &cobra.Command{ pond, _ := pond.NewPond(LogLevel) pond.Init( - "docker", Namespace, ListenAddress, ApiUrl, RpcUrl, + "docker", Namespace, ListenAddress, ApiUrl, RpcUrl, KujiraVersion, Chains, Contracts, Nodes, options, ) }, @@ -78,6 +79,7 @@ func init() { initCmd.PersistentFlags().StringVar(&ListenAddress, "listen", "127.0.0.1", "Set listen address") initCmd.PersistentFlags().StringVar(&ApiUrl, "api-url", "https://rest.cosmos.directory/kujira", "Set API URL") initCmd.PersistentFlags().StringVar(&RpcUrl, "rpc-url", "https://rpc.cosmos.directory/kujira", "Set RPC URL") + initCmd.PersistentFlags().StringVar(&KujiraVersion, "kujira-version", "", "Set Kujira version") initCmd.PersistentFlags().BoolVar(&NoContracts, "no-contracts", false, "Don't deploy contracts on first start") chains, err := templates.GetChains() diff --git a/go.mod b/go.mod index f6df6fa..5bafd36 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module pond -go 1.21.3 +go 1.21 require ( github.com/rs/zerolog v1.32.0 diff --git a/pond/chain/chain.go b/pond/chain/chain.go index 257518c..6b8dfc4 100644 --- a/pond/chain/chain.go +++ b/pond/chain/chain.go @@ -6,11 +6,13 @@ import ( "os" "path/filepath" "strconv" + "strings" "sync" "time" "pond/pond/chain/feeder" "pond/pond/chain/node" + "pond/pond/globals" "pond/utils" "github.com/rs/zerolog" @@ -158,7 +160,9 @@ func (c *Chain) UpdateGenesis(overrides map[string]string) error { "app_state/gov/params/voting_period": "120s", }, "kujira": { - "app_state/mint/minter/inflation": "0.0", + "app_state/mint/minter/inflation": "0.0", + }, + "kujira-99f7924-1": { "app_state/oracle/params/required_denoms": []string{"BTC", "ETH"}, "consensus/params/abci/vote_extensions_enable_height": "1", }, @@ -179,7 +183,12 @@ func (c *Chain) UpdateGenesis(overrides map[string]string) error { return c.error(err) } - for _, key := range []string{"_default", node.Type} { + version, found := globals.Versions[node.Type] + if !found { + return fmt.Errorf("version not found") + } + keys := []string{"_default", node.Type, node.Type + "-" + version} + for _, key := range keys { values, found := config[key] if !found { continue @@ -217,6 +226,9 @@ func (c *Chain) GetHeight() (int64, error) { SyncInfo struct { LatestBlockHeight string `json:"latest_block_height"` } `json:"sync_info"` + SyncInfoOld struct { + LatestBlockHeight string `json:"latest_block_height"` + } `json:"SyncInfo"` } var status Status @@ -231,7 +243,12 @@ func (c *Chain) GetHeight() (int64, error) { return -1, c.error(err) } - height, err := strconv.ParseInt(status.SyncInfo.LatestBlockHeight, 10, 64) + strHeight := status.SyncInfoOld.LatestBlockHeight + if strHeight == "" { + strHeight = status.SyncInfo.LatestBlockHeight + } + + height, err := strconv.ParseInt(strHeight, 10, 64) if err != nil { return -1, c.error(err) } @@ -363,3 +380,37 @@ func (c *Chain) SubmitProposal(data []byte, option string) error { return nil } + +func (c *Chain) WaitForNode(name string) error { + c.logger.Debug().Str("node", name).Msg("wait for node") + + command := []string{c.Command, "ps", "-qf", "name=" + name} + c.logger.Debug().Msg(strings.Join(command, " ")) + + var ( + output []byte + err error + ) + + retries := 10 + for i := 0; i < retries; i++ { + output, err = utils.RunO(c.logger, command) + if err != nil { + return err + } + + if len(output) > 0 { + break + } + + time.Sleep(time.Millisecond * 200) + } + + if len(output) == 0 { + msg := "node not running" + c.logger.Error().Str("node", name).Msg(msg) + return fmt.Errorf(msg) + } + + return nil +} diff --git a/pond/chain/init.go b/pond/chain/init.go index 42e9351..f0d3c9a 100644 --- a/pond/chain/init.go +++ b/pond/chain/init.go @@ -29,7 +29,7 @@ func (c *Chain) Init(namespace string, options map[string]string) error { image := fmt.Sprintf("docker.io/%s/%s:%s", namespace, c.Type, version) - amount := 1_000_000_000_000 + amount := 10_000_000_000_000 var wg sync.WaitGroup @@ -61,6 +61,8 @@ func (c *Chain) Init(namespace string, options map[string]string) error { return } + c.WaitForNode(c.Nodes[i].Moniker) + err = c.Nodes[i].Init(namespace, amount) if err != nil { wg.Done() diff --git a/pond/chain/node/node.go b/pond/chain/node/node.go index 7f37e7c..988c026 100644 --- a/pond/chain/node/node.go +++ b/pond/chain/node/node.go @@ -143,6 +143,18 @@ func (n *Node) Init(namespace string, amount int) error { return err } + for i := 0; i < 10; i++ { + _, err = os.Stat(fmt.Sprintf("%s/config/genesis.json", n.Home)) + if err == nil { + break + } + n.logger.Debug().Msg("wait genesis.json") + time.Sleep(time.Millisecond * 200) + } + if err != nil { + return err + } + err = n.AddKey("validator", n.Mnemonic) if err != nil { n.logger.Err(err) @@ -161,6 +173,24 @@ func (n *Node) Init(namespace string, amount int) error { return err } + // updating the genesis file takes a little bit, so sleep 100ms + // to ensure the address is found and prevent sleeping once for 500ms + time.Sleep(time.Millisecond * 100) + + for i := 0; i < 5; i++ { + data, err := os.ReadFile(fmt.Sprintf("%s/config/genesis.json", n.Home)) + if err != nil { + return err + } + + if strings.Contains(string(data), address) { + break + } + + n.logger.Debug().Msg("wait for genesis account") + time.Sleep(time.Millisecond * 500) + } + err = n.CreateGentx(amount / 2) if err != nil { return err diff --git a/pond/deployer/deployer.go b/pond/deployer/deployer.go index 49f6699..106e926 100644 --- a/pond/deployer/deployer.go +++ b/pond/deployer/deployer.go @@ -13,10 +13,12 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" "sync" "pond/pond/chain/node" + "pond/pond/registry" "pond/utils" "github.com/rs/zerolog" @@ -28,20 +30,22 @@ type Deployer struct { node node.Node Denoms map[string]Denom Contracts map[string]Contract + CodeIds map[string]string addresses map[string]struct{} codes map[string]string plan Plan address string - registry map[string]Code + registry *registry.Registry apiUrl string home string accounts []string // test accounts for minting assets } type Plan struct { - Denoms []Denom `yaml:"denoms"` - Contracts [][]Contract `yaml:"contracts"` - Names []string // holds plan file names, used only for logging + Denoms []Denom `yaml:"denoms"` + Codes map[string]string `yaml:"codes"` + Contracts [][]Contract `yaml:"contracts"` + Names []string // holds plan file names, used only for logging } type CodeMsg struct { @@ -90,6 +94,7 @@ func NewDeployer( node node.Node, apiUrl string, accounts []string, + registry *registry.Registry, ) (Deployer, error) { logger.Debug().Msg("create deployer") @@ -99,31 +104,20 @@ func NewDeployer( plan: Plan{ Denoms: []Denom{}, Contracts: [][]Contract{}, + Codes: map[string]string{}, }, codes: map[string]string{}, Denoms: map[string]Denom{}, Contracts: map[string]Contract{}, + CodeIds: map[string]string{}, address: "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", addresses: map[string]struct{}{}, apiUrl: apiUrl, home: home, accounts: accounts, + registry: registry, } - data, err := os.ReadFile(home + "/registry.json") - if err != nil { - return deployer, err - } - - var registry map[string]Code - err = json.Unmarshal(data, ®istry) - if err != nil { - logger.Err(err) - return deployer, err - } - - deployer.registry = registry - return deployer, nil } @@ -160,7 +154,7 @@ func (d *Deployer) Deploy(filenames []string) error { contentType := http.DetectContentType(buf) switch contentType { - case "text/plain; charset=utf-8": + case "text/plain; charset=utf-8", "application/octet-stream": // return d.DeployPlanfiles([]string{filename}) err := d.LoadPlanFile(filename) if err != nil { @@ -202,19 +196,23 @@ func (d *Deployer) DeployWasmFile(filename string) error { return nil } - err = d.DeployCode(data) + name := strings.Replace(filepath.Base(filename), ".", "_", -1) + + err = d.registry.Set(name, registry.Code{ + Checksum: utils.Sha256(data), + Source: "file://" + filename, + Code: data, + }) if err != nil { return err } - err = d.UpdateDeployedCodes() + err = d.DeployCode(data) if err != nil { return err } - name := filepath.Base(filename) - - return d.UpdateRegistry(name, "file://"+filename, data) + return nil } func (d *Deployer) DeployCode(data []byte) error { @@ -461,6 +459,14 @@ func (d *Deployer) LoadPlan(data []byte, name string) error { d.plan.Denoms = append(d.plan.Denoms, denom) } + // loop is needed to override already set + for code, source := range plan.Codes { + if !strings.HasPrefix(source, "file://") { + continue + } + d.plan.Codes[code] = source + } + d.plan.Contracts = append(d.plan.Contracts, plan.Contracts...) names := make([]string, len(d.plan.Contracts)) @@ -497,7 +503,7 @@ func (d *Deployer) BuildAddress(hash, salt string) (string, error) { return address, nil } -func (d *Deployer) CreateCodeMsgs(codes []Code) ([]json.RawMessage, error) { +func (d *Deployer) CreateCodeMsgs(codes []registry.Code) ([]json.RawMessage, error) { d.logger.Debug().Msg("create code msgs") msgs := make([]json.RawMessage, len(codes)) @@ -615,10 +621,8 @@ func (d *Deployer) CreateContractMsgs( msgs := []json.RawMessage{} for _, contract := range contracts { - code, found := d.registry[contract.Code] - if !found { - err := fmt.Errorf("code not found in registry") - d.logger.Err(err).Str("name", contract.Code).Msg("") + code, err := d.registry.Get(contract.Code) + if err != nil { return nil, err } @@ -629,11 +633,6 @@ func (d *Deployer) CreateContractMsgs( return nil, err } - _, found = contract.Msg["owner"] - if !found { - contract.Msg["owner"] = []byte(`"` + d.address + `"`) - } - hash := sha256.New() hash.Write([]byte(contract.Label)) saltBase64 := base64.StdEncoding.EncodeToString(hash.Sum(nil)) @@ -674,7 +673,10 @@ func (d *Deployer) CreateContractMsgs( return nil, d.error(err) } - msg := buffer.Bytes() + msg, err := d.Convert(buffer.Bytes()) + if err != nil { + return nil, err + } funds, err := d.StringToFunds(contract.Funds) if err != nil { @@ -778,6 +780,14 @@ func (d *Deployer) UpdateDeployedCodes() error { d.codes[code.DataHash] = code.CodeId } + for name, code := range d.registry.Codes() { + codeId, found := d.codes[code.Checksum] + if !found { + continue + } + d.CodeIds[name] = codeId + } + key = info.Pagination.NextKey } @@ -825,32 +835,6 @@ func (d *Deployer) UpdateDeployedContracts() error { return nil } -func (d *Deployer) UpdateRegistry(name, source string, code []byte) error { - _, found := d.registry[name] - if found { - d.logger.Debug().Str("name", name).Msg("code already registered") - return nil - } - - d.registry[name] = Code{ - Checksum: utils.Sha256(code), - Source: source, - Code: code, - } - - data, err := json.Marshal(d.registry) - if err != nil { - return d.error(err) - } - - err = os.WriteFile(d.home+"/registry.json", data, 0o644) - if err != nil { - return d.error(err) - } - - return nil -} - func (d *Deployer) SignAndSend(msgs []json.RawMessage) error { data, err := json.Marshal(msgs) if err != nil { @@ -862,6 +846,8 @@ func (d *Deployer) SignAndSend(msgs []json.RawMessage) error { return err } + d.logger.Trace().Msg(string(data)) + unsigned, err := os.CreateTemp(d.node.Home, "tx") if err != nil { return d.error(err) @@ -879,7 +865,7 @@ func (d *Deployer) SignAndSend(msgs []json.RawMessage) error { d.logger.Debug().Msg("sign tx") output, err := d.node.Tx([]string{ "sign", "/home/kujira/.kujira/" + filepath.Base(unsigned.Name()), - "--from", "deployer", + "--from", "deployer", "--gas", "1000000000", }) if err != nil { return err @@ -920,7 +906,7 @@ func (d *Deployer) SignAndSend(msgs []json.RawMessage) error { return nil } -func (d *Deployer) GetCode(code Code) ([]byte, error) { +func (d *Deployer) GetCode(code registry.Code) ([]byte, error) { parts, err := url.Parse(code.Source) if err != nil { return nil, d.error(err) @@ -955,6 +941,11 @@ func (d *Deployer) GetCode(code Code) ([]byte, error) { return nil, d.error(err) } + // if no checksum is defined, don't try to check it + if code.Checksum == "" { + return data, nil + } + checksum := utils.Sha256(data) if !strings.EqualFold(checksum, code.Checksum) { err = fmt.Errorf("checksum mismatch") @@ -968,20 +959,37 @@ func (d *Deployer) GetCode(code Code) ([]byte, error) { return data, nil } -func (d *Deployer) GetMissingCodes() ([]Code, error) { +func (d *Deployer) GetMissingCodes() ([]registry.Code, error) { d.logger.Debug().Msg("get missing codes") - missing := map[string]Code{} + missing := map[string]registry.Code{} + + // update registry first + // using registry.Codes() to not cause error message at this point + for name, source := range d.plan.Codes { + code, found := d.registry.Codes()[name] + if !found { + code = registry.Code{} + } + + data, err := os.ReadFile(strings.Replace(source, "file://", "", -1)) + if err != nil { + return nil, err + } + + code.Checksum = utils.Sha256(data) + code.Source = source + + d.registry.Set(name, code) + } for _, contracts := range d.plan.Contracts { for _, contract := range contracts { - code, found := d.registry[contract.Code] - if !found { - err := fmt.Errorf("code not found in registry") - d.logger.Err(err).Str("code", contract.Code) + code, err := d.registry.Get(contract.Code) + if err != nil { return nil, err } - _, found = d.codes[code.Checksum] + _, found := d.codes[code.Checksum] if found { // already deployed continue @@ -998,11 +1006,11 @@ func (d *Deployer) GetMissingCodes() ([]Code, error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - codes := []Code{} + codes := []registry.Code{} for _, code := range missing { wg.Add(1) - go func(code Code) { + go func(code registry.Code) { defer wg.Done() select { @@ -1084,11 +1092,11 @@ func (d *Deployer) GetDenomsFromCreator(address string) ([]string, error) { return response.Denoms, nil } -func (d *Deployer) GetDeployedCodes() ([]Code, error) { - codes := []Code{} +func (d *Deployer) GetDeployedCodes() ([]registry.Code, error) { + codes := []registry.Code{} names := map[string]string{} - for name, code := range d.registry { + for name, code := range d.registry.Codes() { names[code.Checksum] = name } @@ -1101,7 +1109,7 @@ func (d *Deployer) GetDeployedCodes() ([]Code, error) { Msg("code not registered") } - codes = append(codes, Code{ + codes = append(codes, registry.Code{ Checksum: checksum, Name: name, Id: id, @@ -1115,10 +1123,8 @@ func (d *Deployer) GetDeployedContracts() ([]Contract, error) { contracts := []Contract{} for _, contract := range d.Contracts { - code, found := d.registry[contract.Code] - if !found { - err := fmt.Errorf("contract not registered") - d.logger.Err(err).Str("name", contract.Code).Msg("") + code, err := d.registry.Get(contract.Code) + if err != nil { return nil, err } @@ -1143,7 +1149,21 @@ func (d *Deployer) StringToFunds(str string) ([]Funds, error) { return funds, nil } - regex := regexp.MustCompile(`^(\d+)([/A-Za-z0-1]+)$`) + tmpl, err := template.New("").Parse(str) + if err != nil { + return nil, d.error(err) + } + + var buffer bytes.Buffer + + err = tmpl.Execute(&buffer, d) + if err != nil { + return nil, d.error(err) + } + + str = buffer.String() + + regex := regexp.MustCompile(`^(\d+)([/A-Za-z0-9]+)$`) for _, part := range strings.Split(str, ",") { matches := regex.FindStringSubmatch(part) @@ -1156,5 +1176,28 @@ func (d *Deployer) StringToFunds(str string) ([]Funds, error) { }) } + sort.Slice(funds, func(i, j int) bool { + return funds[i].Denom < funds[j].Denom + }) + return funds, nil } + +func (d *Deployer) Convert(data []byte) ([]byte, error) { + raw := json.RawMessage(data) + + data, err := json.Marshal(raw) + if err != nil { + return nil, d.error(err) + } + + buffer := new(bytes.Buffer) + err = json.Compact(buffer, data) + if err != nil { + return nil, d.error(err) + } + + regex := regexp.MustCompile(`"((\d+)\s*\|\s*int\s*)"`) + + return []byte(regex.ReplaceAllString(string(data), "$2")), nil +} diff --git a/pond/globals/versions.go b/pond/globals/versions.go index 9357f4d..daa53e6 100644 --- a/pond/globals/versions.go +++ b/pond/globals/versions.go @@ -1,10 +1,10 @@ package globals var Versions = map[string]string{ - "kujira": "99f7924-1", - "cosmoshub": "v16.0.0-1", - "terra2": "v2.11.1-3", - "feeder": "api-only-1", - "relayer": "v2.5.2-1", - "proxy": "v1.0.0-1", + "kujira": "99f7924-2", + "cosmoshub": "v16.0.0-2", + "terra2": "v2.11.1-4", + "feeder": "api-only-2", + "relayer": "v2.5.2-2", + "proxy": "v1.0.0-2", } diff --git a/pond/info.go b/pond/info.go index 6f42ece..39a16f4 100644 --- a/pond/info.go +++ b/pond/info.go @@ -7,7 +7,7 @@ import ( "sort" "pond/pond/chain/node" - "pond/pond/deployer" + "pond/pond/registry" ) type Account struct { @@ -24,7 +24,7 @@ type Contract struct { type Info struct { Validators map[string][]node.Node `json:"validators"` Accounts map[string]Account `json:"accounts"` - Codes []deployer.Code `json:"codes"` + Codes []registry.Code `json:"codes"` Contracts []Contract `json:"contracts"` } @@ -161,6 +161,9 @@ func (i *Info) ListCodes() error { fmt.Printf("%*s checksum name\n", padding, "id") for _, code := range i.Codes { + if code.Name == "" { + continue + } checksum := code.Checksum[:8] + "…" + code.Checksum[56:] lines = append(lines, fmt.Sprintf( diff --git a/pond/init.go b/pond/init.go index 16a69d6..cc82900 100644 --- a/pond/init.go +++ b/pond/init.go @@ -14,7 +14,7 @@ import ( ) func (p *Pond) Init( - command, namespace, address, api, rpc string, + command, namespace, address, api, rpc, version string, chains, plans []string, nodes uint, options map[string]string, @@ -57,6 +57,10 @@ func (p *Pond) Init( Address: address, } + if version != "" { + p.config.Versions["kujira"] = version + } + types := map[string]int{ "kujira": 1, } diff --git a/pond/pond.go b/pond/pond.go index 0598d0c..7d1d4bd 100644 --- a/pond/pond.go +++ b/pond/pond.go @@ -11,6 +11,7 @@ import ( "pond/pond/chain" "pond/pond/chain/node" "pond/pond/deployer" + "pond/pond/registry" "pond/pond/relayer" "pond/pond/templates" "pond/utils" @@ -27,7 +28,7 @@ type Pond struct { relayer relayer.Relayer deployer deployer.Deployer proxy Proxy - registry Registry + registry *registry.Registry } func NewPond(logLevel string) (Pond, error) { @@ -108,15 +109,20 @@ func (p *Pond) init() error { return nil } + p.registry, err = registry.NewRegistry(p.logger, p.home+"/registry.json") + if err != nil { + return err + } + accounts := []string{} for name, account := range p.info.Accounts { - if strings.HasPrefix(name, "test") { + if strings.HasPrefix(name, "test") || name == "deployer" { accounts = append(accounts, account.Addresses["kujira"]) } } p.deployer, err = deployer.NewDeployer( - p.logger, p.home, nodes[0], p.config.ApiUrl, accounts, + p.logger, p.home, nodes[0], p.config.ApiUrl, accounts, p.registry, ) if err != nil { return p.error(err) @@ -127,11 +133,6 @@ func (p *Pond) init() error { return err } - p.registry, err = NewRegistry(p.logger, p.home+"/registry.json") - if err != nil { - return err - } - if len(p.config.Chains) == 1 { return nil } diff --git a/pond/registry.go b/pond/registry.go index e2193ca..1d1367e 100644 --- a/pond/registry.go +++ b/pond/registry.go @@ -1,172 +1,16 @@ package pond -import ( - "encoding/json" - "fmt" - "net/url" - "os" - "sort" - - "pond/pond/deployer" - "pond/utils" - - "github.com/rs/zerolog" -) - -type Registry struct { - logger zerolog.Logger - path string - Data map[string]deployer.Code -} - -func NewRegistry(logger zerolog.Logger, path string) (Registry, error) { - registry := Registry{ - logger: logger, - path: path, - } - - err := registry.Load(path) - if err != nil { - logger.Err(err) - return registry, err - } - - return registry, nil -} - -func (r *Registry) error(err error) error { - r.logger.Err(err).Msg("") - return err -} - -func (r *Registry) Load(filename string) error { - r.logger.Debug().Str("file", filename).Msg("load registry") - - data, err := os.ReadFile(filename) - if err != nil { - return r.error(err) - } - - var items map[string]deployer.Code - err = json.Unmarshal(data, &items) - if err != nil { - return r.error(err) - } - - r.Data = items - - return nil -} - -func (r *Registry) Import(filename string) error { - r.Load(filename) - return r.Save() -} - -func (r *Registry) Save() error { - data, err := json.Marshal(r.Data) - if err != nil { - return r.error(err) - } - - err = os.WriteFile(r.path, data, 0o644) - if err != nil { - return r.error(err) - } - - return nil +func (p *Pond) ListRegistry() error { + return p.registry.List() } -func (r *Registry) Export(filename string) error { - data, err := json.Marshal(r.Data) - if err != nil { - return r.error(err) - } - - err = os.WriteFile(filename, data, 0o644) +func (p *Pond) UpdateRegistry(name string, args map[string]string) error { + err := p.registry.Update(name, args) if err != nil { - return r.error(err) - } - - return nil -} - -func (r *Registry) List() error { - padding := 0 - keys := []string{} - - for key := range r.Data { - keys = append(keys, key) - length := len(key) - if length > padding { - padding = length - } - } - - sort.Strings(keys) - - for _, key := range keys { - code := r.Data[key] - checksum := code.Checksum[:8] + "…" + code.Checksum[56:] - fmt.Printf("%s %-*s %s\n", checksum, padding, key, code.Source) - } - - return nil -} - -func (r *Registry) Update(name string, args map[string]string) error { - r.logger.Debug().Msg("update registry") - - item, found := r.Data[name] - if !found { - err := fmt.Errorf("code not registered") - r.logger.Err(err).Str("name", name).Msg("") return err } - newName, found := args["name"] - if found { - delete(r.Data, name) - name = newName - } - - newSource, found := args["source"] - if found { - parts, err := url.Parse(newSource) - if err != nil { - return r.error(err) - } - - switch parts.Scheme { - case "file": - data, err := os.ReadFile(parts.Path) - if err != nil { - return r.error(err) - } - - item.Checksum = utils.Sha256(data) - case "kaiyo-1": - break - default: - err := fmt.Errorf("scheme not supported") - r.logger.Err(err).Str("scheme", parts.Scheme).Msg("") - return err - } - - item.Source = newSource - } - - r.Data[name] = item - - return r.Save() -} - -func (p *Pond) ListRegistry() error { - return p.registry.List() -} - -func (p *Pond) UpdateRegistry(name string, args map[string]string) error { - return p.registry.Update(name, args) + return p.UpdateCodes() } func (p *Pond) ExportRegistry(filename string) error { @@ -174,5 +18,10 @@ func (p *Pond) ExportRegistry(filename string) error { } func (p *Pond) ImportRegistry(filename string) error { - return p.registry.Import(filename) + err := p.registry.Import(filename) + if err != nil { + return err + } + + return p.UpdateCodes() } diff --git a/pond/registry/registry.go b/pond/registry/registry.go new file mode 100644 index 0000000..0a8f5df --- /dev/null +++ b/pond/registry/registry.go @@ -0,0 +1,201 @@ +package registry + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + "sort" + "strings" + + "pond/utils" + + "github.com/rs/zerolog" +) + +type Code struct { + Id string `json:"id"` + Name string `json:"name"` + Code []byte `json:"-"` + Source string `json:"source,omitempty"` + Checksum string `json:"checksum"` +} + +type Registry struct { + logger zerolog.Logger + path string + Data map[string]Code +} + +func NewRegistry(logger zerolog.Logger, path string) (*Registry, error) { + registry := Registry{ + logger: logger, + path: path, + } + + err := registry.Load(path) + if err != nil { + logger.Err(err) + return nil, err + } + + return ®istry, nil +} + +func (r *Registry) error(err error) error { + r.logger.Err(err).Msg("") + return err +} + +func (r *Registry) Load(filename string) error { + r.logger.Debug().Str("file", filename).Msg("load registry") + + data, err := os.ReadFile(filename) + if err != nil { + return r.error(err) + } + + var items map[string]Code + err = json.Unmarshal(data, &items) + if err != nil { + return r.error(err) + } + + r.Data = map[string]Code{} + for name, item := range items { + r.Data[name] = Code{ + Source: item.Source, + Checksum: strings.ToUpper(item.Checksum), + } + } + + return nil +} + +func (r *Registry) Import(filename string) error { + r.Load(filename) + return r.Save() +} + +func (r *Registry) Save() error { + data, err := json.Marshal(r.Data) + if err != nil { + return r.error(err) + } + + err = os.WriteFile(r.path, data, 0o644) + if err != nil { + return r.error(err) + } + + return nil +} + +func (r *Registry) Export(filename string) error { + data, err := json.Marshal(r.Data) + if err != nil { + return r.error(err) + } + + err = os.WriteFile(filename, data, 0o644) + if err != nil { + return r.error(err) + } + + return nil +} + +func (r *Registry) List() error { + padding := 0 + keys := []string{} + + for key := range r.Data { + keys = append(keys, key) + length := len(key) + if length > padding { + padding = length + } + } + + sort.Strings(keys) + + for _, key := range keys { + code := r.Data[key] + checksum := "................." + if len(code.Checksum) > 57 { + checksum = code.Checksum[:8] + "…" + code.Checksum[56:] + } + + fmt.Printf("%s %-*s %s\n", checksum, padding, key, code.Source) + } + + return nil +} + +func (r *Registry) Update(name string, args map[string]string) error { + r.logger.Debug().Msg("update registry") + + item, found := r.Data[name] + if !found { + err := fmt.Errorf("code not registered") + r.logger.Err(err).Str("name", name).Msg("") + return err + } + + newName, found := args["name"] + if found { + delete(r.Data, name) + name = newName + } + + newSource, found := args["source"] + if found { + parts, err := url.Parse(newSource) + if err != nil { + return r.error(err) + } + + switch parts.Scheme { + case "file": + data, err := os.ReadFile(parts.Path) + if err != nil { + return r.error(err) + } + + item.Checksum = utils.Sha256(data) + case "kaiyo-1": + break + default: + err := fmt.Errorf("scheme not supported") + r.logger.Err(err).Str("scheme", parts.Scheme).Msg("") + return err + } + + item.Source = newSource + } + + r.Data[name] = item + + return r.Save() +} + +func (r *Registry) Get(name string) (Code, error) { + code, found := r.Data[name] + if !found { + msg := "code not found" + r.logger.Error(). + Str("name", name). + Msg(msg) + return Code{}, fmt.Errorf(msg) + } + return code, nil +} + +func (r *Registry) Codes() map[string]Code { + return r.Data +} + +func (r *Registry) Set(name string, code Code) error { + r.Data[name] = code + return r.Save() +} diff --git a/pond/templates/plan/kujira.json b/pond/templates/plan/kujira.json index 376ce08..b4adb7f 100644 --- a/pond/templates/plan/kujira.json +++ b/pond/templates/plan/kujira.json @@ -28,6 +28,7 @@ "label": "USK Controller", "funds": "10000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denom": "uusk" }, "creates": [ @@ -45,6 +46,7 @@ "label": "Ghost Vault USK", "funds": "20000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denom": "{{ .Denoms.USK.Path }}", "oracle": { "static": "1" @@ -100,6 +102,7 @@ "code": "kujira_fin", "label": "Fin KUJI-USK", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denoms": [ { "native": "{{ .Denoms.KUJI.Path }}" @@ -121,6 +124,7 @@ "code": "kujira_fin", "label": "Fin stETH-ETH", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denoms": [ { "native": "{{ .Denoms.stETH.Path }}" @@ -142,6 +146,7 @@ "code": "kujira_fin", "label": "Fin ETH-USK", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denoms": [ { "native": "{{ .Denoms.ETH.Path }}" @@ -163,6 +168,7 @@ "code": "kujira_fin", "label": "Fin USDC-USK", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "denoms": [ { "native": "{{ .Denoms.USDC.Path }}" @@ -184,6 +190,7 @@ "code": "kujira_orca", "label": "Orca ETH-USK", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "bid_denom": "{{ .Denoms.USK.Path }}", "collateral_denom": "{{ .Denoms.ETH.Path }}", "bid_threshold": "10000000000", @@ -200,6 +207,7 @@ "code": "kujira_bow_staking", "label": "Bow Staking", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "incentive_fee": { "amount": "1000000", "denom": "{{ .Denoms.USK.Path }}" @@ -215,6 +223,7 @@ "label": "Bow KUJI-USK", "funds": "10000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "fin_contract": "{{ .Contracts.kujira_fin_kuji_usk.Address }}", "intervals": [ "0.001", @@ -244,6 +253,7 @@ "label": "Bow stETH-ETH", "funds": "10000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "fin_contract": "{{ .Contracts.kujira_fin_steth_eth.Address }}", "adapter": { "oracle": [ @@ -281,6 +291,7 @@ "label": "Bow ETH-USK", "funds": "10000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "fin_contract": "{{ .Contracts.kujira_fin_eth_usk.Address }}", "intervals": [ "0.02", @@ -308,6 +319,7 @@ "label": "Bow USDC-USK", "funds": "10000000ukuji", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "fin_contract": "{{ .Contracts.kujira_fin_usdc_usk.Address }}", "strategy": { "target_price": "1", @@ -335,6 +347,7 @@ "code": "kujira_stable_market", "label": "USK Market ETH", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "stable_denom": "{{ .Denoms.USK.Path }}", "stable_denom_admin": "{{ .Contracts.kujira_stable_mint_usk.Address }}", "collateral_denom": "{{ .Denoms.ETH.Path }}", @@ -365,6 +378,7 @@ "code": "kujira_orca", "label": "Orca USK-KUJI", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "bid_denom": "{{ .Denoms.USK.Path }}", "collateral_denom": "{{ .Denoms.KUJI.Path }}", "bid_threshold": "10000000000", @@ -383,6 +397,7 @@ "code": "kujira_ghost_market", "label": "Ghost Market USK-KUJI", "msg": { + "owner": "kujira1k3g54c2sc7g9mgzuzaukm9pvuzcjqy92nk9wse", "vault_addr": "{{ .Contracts.kujira_ghost_vault_usk.Address }}", "orca_addr": "{{ .Contracts.kujira_orca_usk_kuji.Address }}", "collateral_denom": "{{ .Denoms.KUJI.Path }}",