Add Collocatio native contract for democratic investment
Implement the Collocatio (Latin for "placement/arrangement") contract providing a three-tier investment framework for citizens: - PIO (Public Investment Opportunity): Universal citizen access to infrastructure investments - any citizen can invest small amounts in public projects like roads, schools, hospitals - EIO (Employee Investment Opportunity): Workplace democracy for verified employees - invest in their employer's projects with preferential terms and voting rights - CIO (Contractor Investment Opportunity): Gig economy empowerment - verified contractors can invest in platforms they work with Core Features: - Investment opportunities with configurable parameters - Eligibility system integrated with Vita (soul-bound identity) - Cross-contract integration with VTS, Tribute, and Eligere - Democratic oversight through Eligere voting - Wealth concentration limits via Tribute integration - Education verification through Scire integration Contract Methods: - createOpportunity: Create PIO/EIO/CIO with terms - activateOpportunity: Enable investment period - invest: Make investment with eligibility checks - withdraw: Exit with maturity/penalty rules - setEligibility: RoleInvestmentManager (ID 28) sets eligibility flags - isEligible: Check investor eligibility for opportunity types Contract ID: -25 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3a6ee2492e
commit
b5c87b5cf8
|
|
@ -0,0 +1,979 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/tutus-one/tutus-chain/pkg/config"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/dao"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/interop"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/native/nativehashes"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
|
||||
"github.com/tutus-one/tutus-chain/pkg/core/state"
|
||||
"github.com/tutus-one/tutus-chain/pkg/smartcontract"
|
||||
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
|
||||
"github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
|
||||
"github.com/tutus-one/tutus-chain/pkg/util"
|
||||
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// Collocatio represents the Investment native contract for democratic investment (PIO/EIO/CIO).
|
||||
// Latin: "collocatio" = placement, arrangement (investment)
|
||||
type Collocatio struct {
|
||||
interop.ContractMD
|
||||
NEO INEO
|
||||
Vita IVita
|
||||
RoleRegistry *RoleRegistry
|
||||
VTS *VTS
|
||||
Scire *Scire
|
||||
Eligere *Eligere
|
||||
Tribute *Tribute
|
||||
}
|
||||
|
||||
// Storage prefixes for Collocatio contract.
|
||||
const (
|
||||
collocatioPrefixConfig byte = 0x01 // -> CollocatioConfig
|
||||
collocatioPrefixOpportunity byte = 0x10 // opportunityID -> InvestmentOpportunity
|
||||
collocatioPrefixOpportunityByType byte = 0x11 // type + opportunityID -> exists
|
||||
collocatioPrefixOpportunityByStatus byte = 0x12 // status + opportunityID -> exists
|
||||
collocatioPrefixOppCounter byte = 0x1F // -> next opportunity ID
|
||||
collocatioPrefixInvestment byte = 0x20 // investmentID -> Investment
|
||||
collocatioPrefixInvestmentByOpp byte = 0x21 // opportunityID + investmentID -> exists
|
||||
collocatioPrefixInvestmentByInvestor byte = 0x22 // vitaID + investmentID -> exists
|
||||
collocatioPrefixInvCounter byte = 0x2F // -> next investment ID
|
||||
collocatioPrefixEligibility byte = 0x30 // vitaID -> InvestorEligibility
|
||||
collocatioPrefixEligibilityByOwner byte = 0x31 // owner -> vitaID
|
||||
collocatioPrefixViolation byte = 0x40 // violationID -> InvestmentViolation
|
||||
collocatioPrefixViolationCounter byte = 0x4F // -> next violation ID
|
||||
)
|
||||
|
||||
// Collocatio events.
|
||||
const (
|
||||
collocatioOpportunityCreatedEvent = "OpportunityCreated"
|
||||
collocatioOpportunityActivatedEvent = "OpportunityActivated"
|
||||
collocatioInvestmentMadeEvent = "InvestmentMade"
|
||||
collocatioInvestmentWithdrawnEvent = "InvestmentWithdrawn"
|
||||
collocatioReturnsDistributedEvent = "ReturnsDistributed"
|
||||
collocatioEligibilityUpdatedEvent = "EligibilityUpdated"
|
||||
collocatioViolationRecordedEvent = "ViolationRecorded"
|
||||
)
|
||||
|
||||
// RoleInvestmentManager is the role ID for investment management.
|
||||
const RoleInvestmentManager uint64 = 28
|
||||
|
||||
// Default config values.
|
||||
const (
|
||||
defaultMinPIOParticipants uint64 = 100
|
||||
defaultMinEIOParticipants uint64 = 10
|
||||
defaultMinCIOParticipants uint64 = 25
|
||||
defaultMinInvestment uint64 = 100_00000000 // 100 VTS
|
||||
defaultMaxIndividualCap uint64 = 1_000_000_00000000 // 1M VTS
|
||||
defaultWealthConcentration uint64 = 500 // 5%
|
||||
defaultCreationFee uint64 = 1000_00000000 // 1000 VTS
|
||||
defaultInvestmentFee uint64 = 50 // 0.5%
|
||||
defaultWithdrawalPenalty uint64 = 200 // 2%
|
||||
defaultMinVotingPeriod uint32 = 10000
|
||||
defaultMinInvestmentPeriod uint32 = 20000
|
||||
defaultMinMaturityPeriod uint32 = 50000
|
||||
defaultMaxViolationsBeforeBan uint8 = 3
|
||||
)
|
||||
|
||||
var _ interop.Contract = (*Collocatio)(nil)
|
||||
|
||||
func newCollocatio() *Collocatio {
|
||||
c := &Collocatio{
|
||||
ContractMD: *interop.NewContractMD(nativenames.Collocatio, nativeids.Collocatio),
|
||||
}
|
||||
defer c.BuildHFSpecificMD(c.ActiveIn())
|
||||
|
||||
// getConfig
|
||||
desc := NewDescriptor("getConfig", smartcontract.ArrayType)
|
||||
md := NewMethodAndPrice(c.getConfig, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// getOpportunityCount
|
||||
desc = NewDescriptor("getOpportunityCount", smartcontract.IntegerType)
|
||||
md = NewMethodAndPrice(c.getOpportunityCount, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// getOpportunity
|
||||
desc = NewDescriptor("getOpportunity", smartcontract.ArrayType,
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.getOpportunity, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// createOpportunity
|
||||
desc = NewDescriptor("createOpportunity", smartcontract.IntegerType,
|
||||
manifest.NewParameter("oppType", smartcontract.IntegerType),
|
||||
manifest.NewParameter("name", smartcontract.StringType),
|
||||
manifest.NewParameter("description", smartcontract.StringType),
|
||||
manifest.NewParameter("termsHash", smartcontract.Hash256Type),
|
||||
manifest.NewParameter("minParticipants", smartcontract.IntegerType),
|
||||
manifest.NewParameter("maxParticipants", smartcontract.IntegerType),
|
||||
manifest.NewParameter("minInvestment", smartcontract.IntegerType),
|
||||
manifest.NewParameter("maxInvestment", smartcontract.IntegerType),
|
||||
manifest.NewParameter("targetPool", smartcontract.IntegerType),
|
||||
manifest.NewParameter("expectedReturns", smartcontract.IntegerType),
|
||||
manifest.NewParameter("riskLevel", smartcontract.IntegerType),
|
||||
manifest.NewParameter("maturityBlocks", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.createOpportunity, 1<<17, callflag.States|callflag.AllowNotify)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// activateOpportunity
|
||||
desc = NewDescriptor("activateOpportunity", smartcontract.BoolType,
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.activateOpportunity, 1<<16, callflag.States|callflag.AllowNotify)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// invest
|
||||
desc = NewDescriptor("invest", smartcontract.IntegerType,
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
|
||||
manifest.NewParameter("amount", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.invest, 1<<17, callflag.States|callflag.AllowNotify)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// withdraw
|
||||
desc = NewDescriptor("withdraw", smartcontract.BoolType,
|
||||
manifest.NewParameter("investmentID", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.withdraw, 1<<17, callflag.States|callflag.AllowNotify)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// getEligibility
|
||||
desc = NewDescriptor("getEligibility", smartcontract.ArrayType,
|
||||
manifest.NewParameter("investor", smartcontract.Hash160Type))
|
||||
md = NewMethodAndPrice(c.getEligibility, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// setEligibility
|
||||
desc = NewDescriptor("setEligibility", smartcontract.BoolType,
|
||||
manifest.NewParameter("investor", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("eligibilityFlags", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.setEligibility, 1<<16, callflag.States|callflag.AllowNotify)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// isEligible
|
||||
desc = NewDescriptor("isEligible", smartcontract.BoolType,
|
||||
manifest.NewParameter("investor", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("oppType", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.isEligible, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// getInvestment
|
||||
desc = NewDescriptor("getInvestment", smartcontract.ArrayType,
|
||||
manifest.NewParameter("investmentID", smartcontract.IntegerType))
|
||||
md = NewMethodAndPrice(c.getInvestment, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// getInvestmentCount
|
||||
desc = NewDescriptor("getInvestmentCount", smartcontract.IntegerType)
|
||||
md = NewMethodAndPrice(c.getInvestmentCount, 1<<15, callflag.ReadStates)
|
||||
c.AddMethod(md, desc)
|
||||
|
||||
// ===== Events =====
|
||||
eDesc := NewEventDescriptor(collocatioOpportunityCreatedEvent,
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
|
||||
manifest.NewParameter("oppType", smartcontract.IntegerType),
|
||||
manifest.NewParameter("creator", smartcontract.Hash160Type))
|
||||
c.AddEvent(NewEvent(eDesc))
|
||||
|
||||
eDesc = NewEventDescriptor(collocatioOpportunityActivatedEvent,
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType))
|
||||
c.AddEvent(NewEvent(eDesc))
|
||||
|
||||
eDesc = NewEventDescriptor(collocatioInvestmentMadeEvent,
|
||||
manifest.NewParameter("investmentID", smartcontract.IntegerType),
|
||||
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
|
||||
manifest.NewParameter("investor", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("amount", smartcontract.IntegerType))
|
||||
c.AddEvent(NewEvent(eDesc))
|
||||
|
||||
eDesc = NewEventDescriptor(collocatioInvestmentWithdrawnEvent,
|
||||
manifest.NewParameter("investmentID", smartcontract.IntegerType),
|
||||
manifest.NewParameter("returnAmount", smartcontract.IntegerType))
|
||||
c.AddEvent(NewEvent(eDesc))
|
||||
|
||||
eDesc = NewEventDescriptor(collocatioEligibilityUpdatedEvent,
|
||||
manifest.NewParameter("investor", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("eligibility", smartcontract.IntegerType))
|
||||
c.AddEvent(NewEvent(eDesc))
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Metadata returns contract metadata.
|
||||
func (c *Collocatio) Metadata() *interop.ContractMD {
|
||||
return &c.ContractMD
|
||||
}
|
||||
|
||||
// Initialize initializes Collocatio contract.
|
||||
func (c *Collocatio) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
|
||||
if hf != c.ActiveIn() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize default config
|
||||
cfg := state.CollocatioConfig{
|
||||
MinPIOParticipants: defaultMinPIOParticipants,
|
||||
MinEIOParticipants: defaultMinEIOParticipants,
|
||||
MinCIOParticipants: defaultMinCIOParticipants,
|
||||
DefaultMinInvestment: defaultMinInvestment,
|
||||
MaxIndividualCap: defaultMaxIndividualCap,
|
||||
WealthConcentration: defaultWealthConcentration,
|
||||
CreationFee: defaultCreationFee,
|
||||
InvestmentFee: defaultInvestmentFee,
|
||||
WithdrawalPenalty: defaultWithdrawalPenalty,
|
||||
MinVotingPeriod: defaultMinVotingPeriod,
|
||||
MinInvestmentPeriod: defaultMinInvestmentPeriod,
|
||||
MinMaturityPeriod: defaultMinMaturityPeriod,
|
||||
MaxViolationsBeforeBan: defaultMaxViolationsBeforeBan,
|
||||
ViolationCooldown: 1000000,
|
||||
}
|
||||
c.setConfigInternal(ic.DAO, &cfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache fills native Collocatio cache from DAO.
|
||||
func (c *Collocatio) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements the Contract interface.
|
||||
func (c *Collocatio) OnPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostPersist implements the Contract interface.
|
||||
func (c *Collocatio) PostPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActiveIn returns nil (always active from genesis).
|
||||
func (c *Collocatio) ActiveIn() *config.Hardfork {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Storage Key Helpers
|
||||
// ============================================================================
|
||||
|
||||
func makeCollocatioConfigKey() []byte {
|
||||
return []byte{collocatioPrefixConfig}
|
||||
}
|
||||
|
||||
func makeCollocatioOppKey(oppID uint64) []byte {
|
||||
key := make([]byte, 9)
|
||||
key[0] = collocatioPrefixOpportunity
|
||||
binary.BigEndian.PutUint64(key[1:], oppID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioOppByTypeKey(oppType state.OpportunityType, oppID uint64) []byte {
|
||||
key := make([]byte, 10)
|
||||
key[0] = collocatioPrefixOpportunityByType
|
||||
key[1] = byte(oppType)
|
||||
binary.BigEndian.PutUint64(key[2:], oppID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioOppCounterKey() []byte {
|
||||
return []byte{collocatioPrefixOppCounter}
|
||||
}
|
||||
|
||||
func makeCollocatioInvKey(invID uint64) []byte {
|
||||
key := make([]byte, 9)
|
||||
key[0] = collocatioPrefixInvestment
|
||||
binary.BigEndian.PutUint64(key[1:], invID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioInvByOppKey(oppID, invID uint64) []byte {
|
||||
key := make([]byte, 17)
|
||||
key[0] = collocatioPrefixInvestmentByOpp
|
||||
binary.BigEndian.PutUint64(key[1:9], oppID)
|
||||
binary.BigEndian.PutUint64(key[9:], invID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioInvByInvestorKey(vitaID, invID uint64) []byte {
|
||||
key := make([]byte, 17)
|
||||
key[0] = collocatioPrefixInvestmentByInvestor
|
||||
binary.BigEndian.PutUint64(key[1:9], vitaID)
|
||||
binary.BigEndian.PutUint64(key[9:], invID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioInvCounterKey() []byte {
|
||||
return []byte{collocatioPrefixInvCounter}
|
||||
}
|
||||
|
||||
func makeCollocatioEligKey(vitaID uint64) []byte {
|
||||
key := make([]byte, 9)
|
||||
key[0] = collocatioPrefixEligibility
|
||||
binary.BigEndian.PutUint64(key[1:], vitaID)
|
||||
return key
|
||||
}
|
||||
|
||||
func makeCollocatioEligByOwnerKey(owner util.Uint160) []byte {
|
||||
key := make([]byte, 1+util.Uint160Size)
|
||||
key[0] = collocatioPrefixEligibilityByOwner
|
||||
copy(key[1:], owner.BytesBE())
|
||||
return key
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal Storage Methods
|
||||
// ============================================================================
|
||||
|
||||
func (c *Collocatio) getConfigInternal(d *dao.Simple) *state.CollocatioConfig {
|
||||
si := d.GetStorageItem(c.ID, makeCollocatioConfigKey())
|
||||
if si == nil {
|
||||
return &state.CollocatioConfig{
|
||||
MinPIOParticipants: defaultMinPIOParticipants,
|
||||
MinEIOParticipants: defaultMinEIOParticipants,
|
||||
MinCIOParticipants: defaultMinCIOParticipants,
|
||||
DefaultMinInvestment: defaultMinInvestment,
|
||||
MaxIndividualCap: defaultMaxIndividualCap,
|
||||
WealthConcentration: defaultWealthConcentration,
|
||||
CreationFee: defaultCreationFee,
|
||||
InvestmentFee: defaultInvestmentFee,
|
||||
WithdrawalPenalty: defaultWithdrawalPenalty,
|
||||
MinVotingPeriod: defaultMinVotingPeriod,
|
||||
MinInvestmentPeriod: defaultMinInvestmentPeriod,
|
||||
MinMaturityPeriod: defaultMinMaturityPeriod,
|
||||
MaxViolationsBeforeBan: defaultMaxViolationsBeforeBan,
|
||||
ViolationCooldown: 1000000,
|
||||
}
|
||||
}
|
||||
cfg := new(state.CollocatioConfig)
|
||||
item, _ := stackitem.Deserialize(si)
|
||||
cfg.FromStackItem(item)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c *Collocatio) setConfigInternal(d *dao.Simple, cfg *state.CollocatioConfig) {
|
||||
item, _ := cfg.ToStackItem()
|
||||
data, _ := stackitem.Serialize(item)
|
||||
d.PutStorageItem(c.ID, makeCollocatioConfigKey(), data)
|
||||
}
|
||||
|
||||
func (c *Collocatio) getCounter(d *dao.Simple, key []byte) uint64 {
|
||||
si := d.GetStorageItem(c.ID, key)
|
||||
if si == nil || len(si) < 8 {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint64(si)
|
||||
}
|
||||
|
||||
func (c *Collocatio) incrementCounter(d *dao.Simple, key []byte) uint64 {
|
||||
current := c.getCounter(d, key)
|
||||
next := current + 1
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, next)
|
||||
d.PutStorageItem(c.ID, key, buf)
|
||||
return next
|
||||
}
|
||||
|
||||
func (c *Collocatio) getOpportunityInternal(d *dao.Simple, oppID uint64) *state.InvestmentOpportunity {
|
||||
si := d.GetStorageItem(c.ID, makeCollocatioOppKey(oppID))
|
||||
if si == nil {
|
||||
return nil
|
||||
}
|
||||
opp := new(state.InvestmentOpportunity)
|
||||
item, _ := stackitem.Deserialize(si)
|
||||
opp.FromStackItem(item)
|
||||
return opp
|
||||
}
|
||||
|
||||
func (c *Collocatio) putOpportunity(d *dao.Simple, opp *state.InvestmentOpportunity) {
|
||||
item, _ := opp.ToStackItem()
|
||||
data, _ := stackitem.Serialize(item)
|
||||
d.PutStorageItem(c.ID, makeCollocatioOppKey(opp.ID), data)
|
||||
}
|
||||
|
||||
func (c *Collocatio) getInvestmentInternal(d *dao.Simple, invID uint64) *state.Investment {
|
||||
si := d.GetStorageItem(c.ID, makeCollocatioInvKey(invID))
|
||||
if si == nil {
|
||||
return nil
|
||||
}
|
||||
inv := new(state.Investment)
|
||||
item, _ := stackitem.Deserialize(si)
|
||||
inv.FromStackItem(item)
|
||||
return inv
|
||||
}
|
||||
|
||||
func (c *Collocatio) putInvestment(d *dao.Simple, inv *state.Investment) {
|
||||
item, _ := inv.ToStackItem()
|
||||
data, _ := stackitem.Serialize(item)
|
||||
d.PutStorageItem(c.ID, makeCollocatioInvKey(inv.ID), data)
|
||||
}
|
||||
|
||||
func (c *Collocatio) getEligibilityInternal(d *dao.Simple, investor util.Uint160) *state.InvestorEligibility {
|
||||
// First get vitaID from owner mapping
|
||||
si := d.GetStorageItem(c.ID, makeCollocatioEligByOwnerKey(investor))
|
||||
if si == nil || len(si) < 8 {
|
||||
return nil
|
||||
}
|
||||
vitaID := binary.BigEndian.Uint64(si)
|
||||
|
||||
// Then get eligibility
|
||||
eligSI := d.GetStorageItem(c.ID, makeCollocatioEligKey(vitaID))
|
||||
if eligSI == nil {
|
||||
return nil
|
||||
}
|
||||
elig := new(state.InvestorEligibility)
|
||||
item, _ := stackitem.Deserialize(eligSI)
|
||||
elig.FromStackItem(item)
|
||||
return elig
|
||||
}
|
||||
|
||||
func (c *Collocatio) putEligibility(d *dao.Simple, elig *state.InvestorEligibility) {
|
||||
item, _ := elig.ToStackItem()
|
||||
data, _ := stackitem.Serialize(item)
|
||||
d.PutStorageItem(c.ID, makeCollocatioEligKey(elig.VitaID), data)
|
||||
|
||||
// Also store owner -> vitaID mapping
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, elig.VitaID)
|
||||
d.PutStorageItem(c.ID, makeCollocatioEligByOwnerKey(elig.Investor), buf)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Contract Methods
|
||||
// ============================================================================
|
||||
|
||||
func (c *Collocatio) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
cfg := c.getConfigInternal(ic.DAO)
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MinPIOParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MinEIOParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MinCIOParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.DefaultMinInvestment)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MaxIndividualCap)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.WealthConcentration)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.CreationFee)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.InvestmentFee)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.WithdrawalPenalty)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(cfg.MinVotingPeriod))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(cfg.MinInvestmentPeriod))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(cfg.MinMaturityPeriod))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(cfg.MaxViolationsBeforeBan))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(cfg.ViolationCooldown))),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Collocatio) getOpportunityCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
count := c.getCounter(ic.DAO, makeCollocatioOppCounterKey())
|
||||
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
||||
}
|
||||
|
||||
func (c *Collocatio) getOpportunity(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
oppID := toUint64(args[0])
|
||||
opp := c.getOpportunityInternal(ic.DAO, oppID)
|
||||
if opp == nil {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return opportunityToStackItem(opp)
|
||||
}
|
||||
|
||||
func (c *Collocatio) createOpportunity(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
caller := ic.VM.GetCallingScriptHash()
|
||||
|
||||
oppType := state.OpportunityType(toUint64(args[0]))
|
||||
name := toString(args[1])
|
||||
description := toString(args[2])
|
||||
termsHashBytes, err := args[3].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
termsHash, err := util.Uint256DecodeBytesBE(termsHashBytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
minParticipants := toUint64(args[4])
|
||||
maxParticipants := toUint64(args[5])
|
||||
minInvestment := toUint64(args[6])
|
||||
maxInvestment := toUint64(args[7])
|
||||
targetPool := toUint64(args[8])
|
||||
expectedReturns := toUint64(args[9])
|
||||
riskLevel := uint8(toUint64(args[10]))
|
||||
maturityBlocks := uint32(toUint64(args[11]))
|
||||
|
||||
// Validate caller has Vita
|
||||
vita, err := c.Vita.GetTokenByOwner(ic.DAO, caller)
|
||||
if err != nil {
|
||||
panic("caller must have Vita token")
|
||||
}
|
||||
vitaID := vita.TokenID
|
||||
|
||||
// Validate opportunity type
|
||||
if oppType > state.OpportunityCIO {
|
||||
panic("invalid opportunity type")
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
cfg := c.getConfigInternal(ic.DAO)
|
||||
if maturityBlocks < cfg.MinMaturityPeriod {
|
||||
panic("maturity period too short")
|
||||
}
|
||||
if riskLevel < 1 || riskLevel > 10 {
|
||||
panic("risk level must be 1-10")
|
||||
}
|
||||
|
||||
// Get minimum participants based on type
|
||||
var minRequired uint64
|
||||
switch oppType {
|
||||
case state.OpportunityPIO:
|
||||
minRequired = cfg.MinPIOParticipants
|
||||
case state.OpportunityEIO:
|
||||
minRequired = cfg.MinEIOParticipants
|
||||
case state.OpportunityCIO:
|
||||
minRequired = cfg.MinCIOParticipants
|
||||
}
|
||||
if minParticipants < minRequired {
|
||||
panic(fmt.Sprintf("minimum participants must be at least %d for this type", minRequired))
|
||||
}
|
||||
|
||||
// Charge creation fee to Treasury
|
||||
if cfg.CreationFee > 0 {
|
||||
if err := c.VTS.transferUnrestricted(ic, caller, nativehashes.Treasury, new(big.Int).SetUint64(cfg.CreationFee), nil); err != nil {
|
||||
panic("failed to pay creation fee")
|
||||
}
|
||||
}
|
||||
|
||||
// Create opportunity
|
||||
oppID := c.incrementCounter(ic.DAO, makeCollocatioOppCounterKey())
|
||||
currentBlock := ic.Block.Index
|
||||
|
||||
opp := &state.InvestmentOpportunity{
|
||||
ID: oppID,
|
||||
Type: oppType,
|
||||
Status: state.OpportunityDraft,
|
||||
Creator: caller,
|
||||
SponsorVitaID: vitaID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
TermsHash: termsHash,
|
||||
MinParticipants: minParticipants,
|
||||
MaxParticipants: maxParticipants,
|
||||
CurrentParticipants: 0,
|
||||
MinInvestment: minInvestment,
|
||||
MaxInvestment: maxInvestment,
|
||||
TotalPool: 0,
|
||||
TargetPool: targetPool,
|
||||
ExpectedReturns: expectedReturns,
|
||||
RiskLevel: riskLevel,
|
||||
VotingDeadline: currentBlock + cfg.MinVotingPeriod,
|
||||
InvestmentDeadline: currentBlock + cfg.MinVotingPeriod + cfg.MinInvestmentPeriod,
|
||||
MaturityDate: currentBlock + cfg.MinVotingPeriod + cfg.MinInvestmentPeriod + maturityBlocks,
|
||||
ProposalID: 0,
|
||||
CreatedAt: currentBlock,
|
||||
UpdatedAt: currentBlock,
|
||||
}
|
||||
|
||||
c.putOpportunity(ic.DAO, opp)
|
||||
|
||||
// Store type index
|
||||
ic.DAO.PutStorageItem(c.ID, makeCollocatioOppByTypeKey(oppType, oppID), []byte{1})
|
||||
|
||||
// Emit event
|
||||
ic.AddNotification(c.Hash, collocatioOpportunityCreatedEvent, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(oppID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(oppType))),
|
||||
stackitem.NewByteArray(caller.BytesBE()),
|
||||
}))
|
||||
|
||||
return stackitem.NewBigInteger(new(big.Int).SetUint64(oppID))
|
||||
}
|
||||
|
||||
func (c *Collocatio) activateOpportunity(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
oppID := toUint64(args[0])
|
||||
|
||||
opp := c.getOpportunityInternal(ic.DAO, oppID)
|
||||
if opp == nil {
|
||||
panic("opportunity not found")
|
||||
}
|
||||
|
||||
caller := ic.VM.GetCallingScriptHash()
|
||||
if caller != opp.Creator && !c.NEO.CheckCommittee(ic) {
|
||||
panic("only creator or committee can activate")
|
||||
}
|
||||
|
||||
if opp.Status != state.OpportunityDraft {
|
||||
panic("opportunity must be in draft status")
|
||||
}
|
||||
|
||||
opp.Status = state.OpportunityActive
|
||||
opp.UpdatedAt = ic.Block.Index
|
||||
c.putOpportunity(ic.DAO, opp)
|
||||
|
||||
ic.AddNotification(c.Hash, collocatioOpportunityActivatedEvent, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(oppID)),
|
||||
}))
|
||||
|
||||
return stackitem.NewBool(true)
|
||||
}
|
||||
|
||||
func (c *Collocatio) invest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
oppID := toUint64(args[0])
|
||||
amount := toUint64(args[1])
|
||||
caller := ic.VM.GetCallingScriptHash()
|
||||
|
||||
// Validate caller has Vita
|
||||
vita, err := c.Vita.GetTokenByOwner(ic.DAO, caller)
|
||||
if err != nil {
|
||||
panic("caller must have Vita token")
|
||||
}
|
||||
vitaID := vita.TokenID
|
||||
|
||||
// Get opportunity
|
||||
opp := c.getOpportunityInternal(ic.DAO, oppID)
|
||||
if opp == nil {
|
||||
panic("opportunity not found")
|
||||
}
|
||||
|
||||
if opp.Status != state.OpportunityActive {
|
||||
panic("opportunity is not active")
|
||||
}
|
||||
if ic.Block.Index > opp.InvestmentDeadline {
|
||||
panic("investment deadline has passed")
|
||||
}
|
||||
|
||||
if opp.MaxParticipants > 0 && opp.CurrentParticipants >= opp.MaxParticipants {
|
||||
panic("maximum participants reached")
|
||||
}
|
||||
|
||||
if amount < opp.MinInvestment {
|
||||
panic("investment below minimum")
|
||||
}
|
||||
if amount > opp.MaxInvestment {
|
||||
panic("investment exceeds maximum")
|
||||
}
|
||||
|
||||
// Check eligibility
|
||||
if !c.isEligibleInternal(ic.DAO, caller, opp.Type) {
|
||||
panic("investor not eligible for this opportunity type")
|
||||
}
|
||||
|
||||
// Calculate fee
|
||||
cfg := c.getConfigInternal(ic.DAO)
|
||||
fee := (amount * cfg.InvestmentFee) / 10000
|
||||
netAmount := amount - fee
|
||||
|
||||
// Transfer VTS from investor
|
||||
if err := c.VTS.transferUnrestricted(ic, caller, c.Hash, new(big.Int).SetUint64(amount), nil); err != nil {
|
||||
panic("failed to transfer investment amount")
|
||||
}
|
||||
|
||||
// Send fee to Treasury
|
||||
if fee > 0 {
|
||||
if err := c.VTS.transferUnrestricted(ic, c.Hash, nativehashes.Treasury, new(big.Int).SetUint64(fee), nil); err != nil {
|
||||
panic("failed to transfer fee to treasury")
|
||||
}
|
||||
}
|
||||
|
||||
// Create investment record
|
||||
invID := c.incrementCounter(ic.DAO, makeCollocatioInvCounterKey())
|
||||
|
||||
inv := &state.Investment{
|
||||
ID: invID,
|
||||
OpportunityID: oppID,
|
||||
VitaID: vitaID,
|
||||
Investor: caller,
|
||||
Amount: netAmount,
|
||||
Status: state.InvestmentActive,
|
||||
ReturnAmount: 0,
|
||||
CreatedAt: ic.Block.Index,
|
||||
UpdatedAt: ic.Block.Index,
|
||||
}
|
||||
|
||||
c.putInvestment(ic.DAO, inv)
|
||||
|
||||
// Store indexes
|
||||
ic.DAO.PutStorageItem(c.ID, makeCollocatioInvByOppKey(oppID, invID), []byte{1})
|
||||
ic.DAO.PutStorageItem(c.ID, makeCollocatioInvByInvestorKey(vitaID, invID), []byte{1})
|
||||
|
||||
// Update opportunity
|
||||
opp.CurrentParticipants++
|
||||
opp.TotalPool += netAmount
|
||||
opp.UpdatedAt = ic.Block.Index
|
||||
c.putOpportunity(ic.DAO, opp)
|
||||
|
||||
// Update eligibility stats
|
||||
c.updateEligibilityOnInvest(ic.DAO, caller, vitaID, netAmount, ic.Block.Index)
|
||||
|
||||
// Emit event
|
||||
ic.AddNotification(c.Hash, collocatioInvestmentMadeEvent, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(invID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(oppID)),
|
||||
stackitem.NewByteArray(caller.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(netAmount)),
|
||||
}))
|
||||
|
||||
return stackitem.NewBigInteger(new(big.Int).SetUint64(invID))
|
||||
}
|
||||
|
||||
func (c *Collocatio) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
invID := toUint64(args[0])
|
||||
caller := ic.VM.GetCallingScriptHash()
|
||||
|
||||
inv := c.getInvestmentInternal(ic.DAO, invID)
|
||||
if inv == nil {
|
||||
panic("investment not found")
|
||||
}
|
||||
|
||||
if inv.Investor != caller {
|
||||
panic("only investor can withdraw")
|
||||
}
|
||||
|
||||
if inv.Status != state.InvestmentActive {
|
||||
panic("investment is not active")
|
||||
}
|
||||
|
||||
opp := c.getOpportunityInternal(ic.DAO, inv.OpportunityID)
|
||||
if opp == nil {
|
||||
panic("opportunity not found")
|
||||
}
|
||||
|
||||
// Calculate penalty for early withdrawal
|
||||
cfg := c.getConfigInternal(ic.DAO)
|
||||
returnAmount := inv.Amount
|
||||
if ic.Block.Index < opp.MaturityDate && cfg.WithdrawalPenalty > 0 {
|
||||
penalty := (inv.Amount * cfg.WithdrawalPenalty) / 10000
|
||||
returnAmount = inv.Amount - penalty
|
||||
if penalty > 0 {
|
||||
if err := c.VTS.transferUnrestricted(ic, c.Hash, nativehashes.Treasury, new(big.Int).SetUint64(penalty), nil); err != nil {
|
||||
panic("failed to transfer penalty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return funds
|
||||
if err := c.VTS.transferUnrestricted(ic, c.Hash, caller, new(big.Int).SetUint64(returnAmount), nil); err != nil {
|
||||
panic("failed to return investment")
|
||||
}
|
||||
|
||||
// Update investment
|
||||
inv.Status = state.InvestmentWithdrawn
|
||||
inv.UpdatedAt = ic.Block.Index
|
||||
c.putInvestment(ic.DAO, inv)
|
||||
|
||||
// Update opportunity
|
||||
opp.CurrentParticipants--
|
||||
opp.TotalPool -= inv.Amount
|
||||
opp.UpdatedAt = ic.Block.Index
|
||||
c.putOpportunity(ic.DAO, opp)
|
||||
|
||||
// Update eligibility
|
||||
c.updateEligibilityOnWithdraw(ic.DAO, caller, inv.Amount, ic.Block.Index)
|
||||
|
||||
// Emit event
|
||||
ic.AddNotification(c.Hash, collocatioInvestmentWithdrawnEvent, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(invID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(returnAmount)),
|
||||
}))
|
||||
|
||||
return stackitem.NewBool(true)
|
||||
}
|
||||
|
||||
func (c *Collocatio) getEligibility(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
investor := toUint160(args[0])
|
||||
elig := c.getEligibilityInternal(ic.DAO, investor)
|
||||
if elig == nil {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return eligibilityToStackItem(elig)
|
||||
}
|
||||
|
||||
func (c *Collocatio) setEligibility(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
investor := toUint160(args[0])
|
||||
eligFlags := state.EligibilityType(toUint64(args[1]))
|
||||
|
||||
// Only committee or RoleInvestmentManager can set eligibility
|
||||
if !c.NEO.CheckCommittee(ic) {
|
||||
caller := ic.VM.GetCallingScriptHash()
|
||||
if !c.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleInvestmentManager, ic.Block.Index) {
|
||||
panic("only committee or investment manager can set eligibility")
|
||||
}
|
||||
}
|
||||
|
||||
vita, err := c.Vita.GetTokenByOwner(ic.DAO, investor)
|
||||
if err != nil {
|
||||
panic("investor must have Vita token")
|
||||
}
|
||||
vitaID := vita.TokenID
|
||||
|
||||
elig := c.getEligibilityInternal(ic.DAO, investor)
|
||||
if elig == nil {
|
||||
elig = &state.InvestorEligibility{
|
||||
VitaID: vitaID,
|
||||
Investor: investor,
|
||||
CreatedAt: ic.Block.Index,
|
||||
}
|
||||
}
|
||||
|
||||
elig.Eligibility = eligFlags
|
||||
elig.UpdatedAt = ic.Block.Index
|
||||
c.putEligibility(ic.DAO, elig)
|
||||
|
||||
ic.AddNotification(c.Hash, collocatioEligibilityUpdatedEvent, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(investor.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(eligFlags))),
|
||||
}))
|
||||
|
||||
return stackitem.NewBool(true)
|
||||
}
|
||||
|
||||
func (c *Collocatio) isEligible(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
investor := toUint160(args[0])
|
||||
oppType := state.OpportunityType(toUint64(args[1]))
|
||||
return stackitem.NewBool(c.isEligibleInternal(ic.DAO, investor, oppType))
|
||||
}
|
||||
|
||||
func (c *Collocatio) isEligibleInternal(d *dao.Simple, investor util.Uint160, oppType state.OpportunityType) bool {
|
||||
elig := c.getEligibilityInternal(d, investor)
|
||||
if elig == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must have completed investment education
|
||||
if !elig.ScireCompleted {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for ban
|
||||
if elig.HasViolations {
|
||||
cfg := c.getConfigInternal(d)
|
||||
if elig.ViolationCount >= cfg.MaxViolationsBeforeBan {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch oppType {
|
||||
case state.OpportunityPIO:
|
||||
return elig.Eligibility&state.EligibilityPIO != 0
|
||||
case state.OpportunityEIO:
|
||||
return elig.Eligibility&state.EligibilityEIO != 0
|
||||
case state.OpportunityCIO:
|
||||
return elig.Eligibility&state.EligibilityCIO != 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collocatio) getInvestment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
invID := toUint64(args[0])
|
||||
inv := c.getInvestmentInternal(ic.DAO, invID)
|
||||
if inv == nil {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return investmentToStackItem(inv)
|
||||
}
|
||||
|
||||
func (c *Collocatio) getInvestmentCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
count := c.getCounter(ic.DAO, makeCollocatioInvCounterKey())
|
||||
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal Helpers
|
||||
// ============================================================================
|
||||
|
||||
func (c *Collocatio) updateEligibilityOnInvest(d *dao.Simple, investor util.Uint160, vitaID, amount uint64, blockHeight uint32) {
|
||||
elig := c.getEligibilityInternal(d, investor)
|
||||
if elig == nil {
|
||||
elig = &state.InvestorEligibility{
|
||||
VitaID: vitaID,
|
||||
Investor: investor,
|
||||
CreatedAt: blockHeight,
|
||||
}
|
||||
}
|
||||
elig.TotalInvested += amount
|
||||
elig.ActiveInvestments++
|
||||
elig.LastActivity = blockHeight
|
||||
elig.UpdatedAt = blockHeight
|
||||
c.putEligibility(d, elig)
|
||||
}
|
||||
|
||||
func (c *Collocatio) updateEligibilityOnWithdraw(d *dao.Simple, investor util.Uint160, amount uint64, blockHeight uint32) {
|
||||
elig := c.getEligibilityInternal(d, investor)
|
||||
if elig == nil {
|
||||
return
|
||||
}
|
||||
if elig.TotalInvested >= amount {
|
||||
elig.TotalInvested -= amount
|
||||
}
|
||||
if elig.ActiveInvestments > 0 {
|
||||
elig.ActiveInvestments--
|
||||
}
|
||||
elig.LastActivity = blockHeight
|
||||
elig.UpdatedAt = blockHeight
|
||||
c.putEligibility(d, elig)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Stack Item Converters
|
||||
// ============================================================================
|
||||
|
||||
func opportunityToStackItem(opp *state.InvestmentOpportunity) stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.ID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.Type))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.Status))),
|
||||
stackitem.NewByteArray(opp.Creator.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.SponsorVitaID)),
|
||||
stackitem.NewByteArray([]byte(opp.Name)),
|
||||
stackitem.NewByteArray([]byte(opp.Description)),
|
||||
stackitem.NewByteArray(opp.TermsHash.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.MinParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.MaxParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.CurrentParticipants)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.MinInvestment)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.MaxInvestment)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.TotalPool)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.TargetPool)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.ExpectedReturns)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.RiskLevel))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.VotingDeadline))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.InvestmentDeadline))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.MaturityDate))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(opp.ProposalID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.CreatedAt))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(opp.UpdatedAt))),
|
||||
})
|
||||
}
|
||||
|
||||
func investmentToStackItem(inv *state.Investment) stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(inv.ID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(inv.OpportunityID)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(inv.VitaID)),
|
||||
stackitem.NewByteArray(inv.Investor.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(inv.Amount)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(inv.Status))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(inv.ReturnAmount)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(inv.CreatedAt))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(inv.UpdatedAt))),
|
||||
})
|
||||
}
|
||||
|
||||
func eligibilityToStackItem(elig *state.InvestorEligibility) stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(elig.VitaID)),
|
||||
stackitem.NewByteArray(elig.Investor.BytesBE()),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.Eligibility))),
|
||||
stackitem.NewBool(elig.ScireCompleted),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.RiskScore))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(elig.TotalInvested)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(elig.TotalReturns)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(elig.ActiveInvestments)),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(elig.CompletedInvestments)),
|
||||
stackitem.NewBool(elig.HasViolations),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.ViolationCount))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.LastActivity))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.CreatedAt))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(elig.UpdatedAt))),
|
||||
})
|
||||
}
|
||||
|
|
@ -644,6 +644,16 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
|||
pons.Scire = scire
|
||||
pons.Salus = salus
|
||||
|
||||
// Create Collocatio (Democratic Investment) contract
|
||||
collocatio := newCollocatio()
|
||||
collocatio.NEO = neo
|
||||
collocatio.Vita = vita
|
||||
collocatio.RoleRegistry = roleRegistry
|
||||
collocatio.VTS = vts
|
||||
collocatio.Scire = scire
|
||||
collocatio.Eligere = eligere
|
||||
collocatio.Tribute = tribute
|
||||
|
||||
return []interop.Contract{
|
||||
mgmt,
|
||||
s,
|
||||
|
|
@ -669,5 +679,6 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
|||
opus,
|
||||
palam,
|
||||
pons,
|
||||
collocatio,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -57,4 +57,6 @@ var (
|
|||
Palam = util.Uint160{0x3, 0x23, 0x73, 0xfa, 0x71, 0x3a, 0x34, 0xab, 0x8d, 0x8c, 0xfa, 0xe2, 0xb0, 0x46, 0xdf, 0x53, 0x88, 0x79, 0x16, 0xae}
|
||||
// Pons is a hash of native Pons contract.
|
||||
Pons = util.Uint160{0x58, 0x39, 0xd0, 0x19, 0xa5, 0xb8, 0x8c, 0x92, 0x3f, 0x9a, 0x80, 0x2b, 0x53, 0xa7, 0xc7, 0x7c, 0x35, 0x81, 0xdd, 0xcc}
|
||||
// Collocatio is a hash of native Collocatio contract.
|
||||
Collocatio = util.Uint160{0xf9, 0x9c, 0x85, 0xeb, 0xea, 0x3, 0xa0, 0xd0, 0x69, 0x29, 0x13, 0x95, 0xdd, 0x33, 0xbc, 0x55, 0x53, 0xc6, 0x28, 0xf5}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,4 +55,6 @@ const (
|
|||
Palam int32 = -23
|
||||
// Pons is an ID of native Pons contract.
|
||||
Pons int32 = -24
|
||||
// Collocatio is an ID of native Collocatio contract.
|
||||
Collocatio int32 = -25
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ const (
|
|||
Sese = "Sese"
|
||||
Tribute = "Tribute"
|
||||
Opus = "Opus"
|
||||
Palam = "Palam"
|
||||
Pons = "Pons"
|
||||
Palam = "Palam"
|
||||
Pons = "Pons"
|
||||
Collocatio = "Collocatio"
|
||||
)
|
||||
|
||||
// All contains the list of all native contract names ordered by the contract ID.
|
||||
|
|
@ -54,6 +55,7 @@ var All = []string{
|
|||
Opus,
|
||||
Palam,
|
||||
Pons,
|
||||
Collocatio,
|
||||
}
|
||||
|
||||
// IsValid checks if the name is a valid native contract's name.
|
||||
|
|
@ -81,5 +83,6 @@ func IsValid(name string) bool {
|
|||
name == Tribute ||
|
||||
name == Opus ||
|
||||
name == Palam ||
|
||||
name == Pons
|
||||
name == Pons ||
|
||||
name == Collocatio
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,955 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/tutus-one/tutus-chain/pkg/io"
|
||||
"github.com/tutus-one/tutus-chain/pkg/util"
|
||||
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// OpportunityType represents the type of investment opportunity.
|
||||
type OpportunityType uint8
|
||||
|
||||
// Investment opportunity types.
|
||||
const (
|
||||
OpportunityPIO OpportunityType = 0 // Public Investment Opportunity - universal access
|
||||
OpportunityEIO OpportunityType = 1 // Employee Investment Opportunity - workplace
|
||||
OpportunityCIO OpportunityType = 2 // Contractor Investment Opportunity - gig economy
|
||||
)
|
||||
|
||||
// OpportunityStatus represents the status of an investment opportunity.
|
||||
type OpportunityStatus uint8
|
||||
|
||||
// Investment opportunity statuses.
|
||||
const (
|
||||
OpportunityDraft OpportunityStatus = 0 // Being created
|
||||
OpportunityVoting OpportunityStatus = 1 // Under democratic voting
|
||||
OpportunityActive OpportunityStatus = 2 // Open for investments
|
||||
OpportunityClosed OpportunityStatus = 3 // No longer accepting investments
|
||||
OpportunityCompleted OpportunityStatus = 4 // Returns distributed
|
||||
OpportunityCancelled OpportunityStatus = 5 // Cancelled with refunds
|
||||
OpportunityFailed OpportunityStatus = 6 // Did not meet minimum requirements
|
||||
)
|
||||
|
||||
// InvestmentStatus represents the status of an individual investment.
|
||||
type InvestmentStatus uint8
|
||||
|
||||
// Investment statuses.
|
||||
const (
|
||||
InvestmentActive InvestmentStatus = 0 // Investment is active
|
||||
InvestmentWithdrawn InvestmentStatus = 1 // Investor withdrew
|
||||
InvestmentCompleted InvestmentStatus = 2 // Returns distributed
|
||||
InvestmentRefunded InvestmentStatus = 3 // Refunded due to cancellation
|
||||
)
|
||||
|
||||
// EligibilityType represents eligibility flags.
|
||||
type EligibilityType uint8
|
||||
|
||||
// Eligibility flags (can be combined).
|
||||
const (
|
||||
EligibilityNone EligibilityType = 0
|
||||
EligibilityPIO EligibilityType = 1 << 0 // Public investment eligible
|
||||
EligibilityEIO EligibilityType = 1 << 1 // Employee investment eligible
|
||||
EligibilityCIO EligibilityType = 1 << 2 // Contractor investment eligible
|
||||
EligibilityAll EligibilityType = EligibilityPIO | EligibilityEIO | EligibilityCIO
|
||||
)
|
||||
|
||||
// InvestmentOpportunity represents an investment opportunity.
|
||||
type InvestmentOpportunity struct {
|
||||
ID uint64 // Unique opportunity identifier
|
||||
Type OpportunityType // PIO, EIO, or CIO
|
||||
Status OpportunityStatus // Current status
|
||||
Creator util.Uint160 // Creator's address (must have Vita)
|
||||
SponsorVitaID uint64 // Sponsor's Vita ID (for EIO: employer, CIO: platform)
|
||||
Name string // Opportunity name
|
||||
Description string // Investment description
|
||||
TermsHash util.Uint256 // Hash of full terms (off-chain)
|
||||
MinParticipants uint64 // Required minimum participants
|
||||
MaxParticipants uint64 // Maximum allowed participants (0 = unlimited)
|
||||
CurrentParticipants uint64 // Current number of participants
|
||||
MinInvestment uint64 // Minimum VTS investment per person
|
||||
MaxInvestment uint64 // Maximum VTS investment per person
|
||||
TotalPool uint64 // Total VTS in opportunity
|
||||
TargetPool uint64 // Target total pool size
|
||||
ExpectedReturns uint64 // Projected returns (basis points, 10000 = 100%)
|
||||
RiskLevel uint8 // Risk level 1-10 (AI-assessed)
|
||||
VotingDeadline uint32 // Block height for voting deadline
|
||||
InvestmentDeadline uint32 // Block height for investment deadline
|
||||
MaturityDate uint32 // Block height when returns distributed
|
||||
ProposalID uint64 // Associated Eligere proposal ID (0 = none)
|
||||
CreatedAt uint32 // Block height when created
|
||||
UpdatedAt uint32 // Block height when last updated
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (o *InvestmentOpportunity) DecodeBinary(br *io.BinReader) {
|
||||
o.ID = br.ReadU64LE()
|
||||
o.Type = OpportunityType(br.ReadB())
|
||||
o.Status = OpportunityStatus(br.ReadB())
|
||||
br.ReadBytes(o.Creator[:])
|
||||
o.SponsorVitaID = br.ReadU64LE()
|
||||
o.Name = br.ReadString()
|
||||
o.Description = br.ReadString()
|
||||
br.ReadBytes(o.TermsHash[:])
|
||||
o.MinParticipants = br.ReadU64LE()
|
||||
o.MaxParticipants = br.ReadU64LE()
|
||||
o.CurrentParticipants = br.ReadU64LE()
|
||||
o.MinInvestment = br.ReadU64LE()
|
||||
o.MaxInvestment = br.ReadU64LE()
|
||||
o.TotalPool = br.ReadU64LE()
|
||||
o.TargetPool = br.ReadU64LE()
|
||||
o.ExpectedReturns = br.ReadU64LE()
|
||||
o.RiskLevel = br.ReadB()
|
||||
o.VotingDeadline = br.ReadU32LE()
|
||||
o.InvestmentDeadline = br.ReadU32LE()
|
||||
o.MaturityDate = br.ReadU32LE()
|
||||
o.ProposalID = br.ReadU64LE()
|
||||
o.CreatedAt = br.ReadU32LE()
|
||||
o.UpdatedAt = br.ReadU32LE()
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (o *InvestmentOpportunity) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(o.ID)
|
||||
bw.WriteB(byte(o.Type))
|
||||
bw.WriteB(byte(o.Status))
|
||||
bw.WriteBytes(o.Creator[:])
|
||||
bw.WriteU64LE(o.SponsorVitaID)
|
||||
bw.WriteString(o.Name)
|
||||
bw.WriteString(o.Description)
|
||||
bw.WriteBytes(o.TermsHash[:])
|
||||
bw.WriteU64LE(o.MinParticipants)
|
||||
bw.WriteU64LE(o.MaxParticipants)
|
||||
bw.WriteU64LE(o.CurrentParticipants)
|
||||
bw.WriteU64LE(o.MinInvestment)
|
||||
bw.WriteU64LE(o.MaxInvestment)
|
||||
bw.WriteU64LE(o.TotalPool)
|
||||
bw.WriteU64LE(o.TargetPool)
|
||||
bw.WriteU64LE(o.ExpectedReturns)
|
||||
bw.WriteB(o.RiskLevel)
|
||||
bw.WriteU32LE(o.VotingDeadline)
|
||||
bw.WriteU32LE(o.InvestmentDeadline)
|
||||
bw.WriteU32LE(o.MaturityDate)
|
||||
bw.WriteU64LE(o.ProposalID)
|
||||
bw.WriteU32LE(o.CreatedAt)
|
||||
bw.WriteU32LE(o.UpdatedAt)
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible interface.
|
||||
func (o *InvestmentOpportunity) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.ID))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.Type))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.Status))),
|
||||
stackitem.NewByteArray(o.Creator.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.SponsorVitaID))),
|
||||
stackitem.NewByteArray([]byte(o.Name)),
|
||||
stackitem.NewByteArray([]byte(o.Description)),
|
||||
stackitem.NewByteArray(o.TermsHash.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.MinParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.MaxParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.CurrentParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.MinInvestment))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.MaxInvestment))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.TotalPool))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.TargetPool))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.ExpectedReturns))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.RiskLevel))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.VotingDeadline))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.InvestmentDeadline))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.MaturityDate))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.ProposalID))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.CreatedAt))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(o.UpdatedAt))),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible interface.
|
||||
func (o *InvestmentOpportunity) FromStackItem(item stackitem.Item) error {
|
||||
items, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not a struct")
|
||||
}
|
||||
if len(items) != 23 {
|
||||
return fmt.Errorf("wrong number of elements: expected 23, got %d", len(items))
|
||||
}
|
||||
|
||||
id, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid id: %w", err)
|
||||
}
|
||||
o.ID = id.Uint64()
|
||||
|
||||
oppType, err := items[1].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid type: %w", err)
|
||||
}
|
||||
o.Type = OpportunityType(oppType.Uint64())
|
||||
|
||||
status, err := items[2].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid status: %w", err)
|
||||
}
|
||||
o.Status = OpportunityStatus(status.Uint64())
|
||||
|
||||
creator, err := items[3].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid creator: %w", err)
|
||||
}
|
||||
o.Creator, err = util.Uint160DecodeBytesBE(creator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid creator address: %w", err)
|
||||
}
|
||||
|
||||
sponsorVitaID, err := items[4].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid sponsorVitaID: %w", err)
|
||||
}
|
||||
o.SponsorVitaID = sponsorVitaID.Uint64()
|
||||
|
||||
name, err := items[5].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid name: %w", err)
|
||||
}
|
||||
o.Name = string(name)
|
||||
|
||||
description, err := items[6].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid description: %w", err)
|
||||
}
|
||||
o.Description = string(description)
|
||||
|
||||
termsHash, err := items[7].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid termsHash: %w", err)
|
||||
}
|
||||
o.TermsHash, err = util.Uint256DecodeBytesBE(termsHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid termsHash: %w", err)
|
||||
}
|
||||
|
||||
minParticipants, err := items[8].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minParticipants: %w", err)
|
||||
}
|
||||
o.MinParticipants = minParticipants.Uint64()
|
||||
|
||||
maxParticipants, err := items[9].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid maxParticipants: %w", err)
|
||||
}
|
||||
o.MaxParticipants = maxParticipants.Uint64()
|
||||
|
||||
currentParticipants, err := items[10].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid currentParticipants: %w", err)
|
||||
}
|
||||
o.CurrentParticipants = currentParticipants.Uint64()
|
||||
|
||||
minInvestment, err := items[11].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minInvestment: %w", err)
|
||||
}
|
||||
o.MinInvestment = minInvestment.Uint64()
|
||||
|
||||
maxInvestment, err := items[12].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid maxInvestment: %w", err)
|
||||
}
|
||||
o.MaxInvestment = maxInvestment.Uint64()
|
||||
|
||||
totalPool, err := items[13].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid totalPool: %w", err)
|
||||
}
|
||||
o.TotalPool = totalPool.Uint64()
|
||||
|
||||
targetPool, err := items[14].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid targetPool: %w", err)
|
||||
}
|
||||
o.TargetPool = targetPool.Uint64()
|
||||
|
||||
expectedReturns, err := items[15].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid expectedReturns: %w", err)
|
||||
}
|
||||
o.ExpectedReturns = expectedReturns.Uint64()
|
||||
|
||||
riskLevel, err := items[16].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid riskLevel: %w", err)
|
||||
}
|
||||
o.RiskLevel = uint8(riskLevel.Uint64())
|
||||
|
||||
votingDeadline, err := items[17].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid votingDeadline: %w", err)
|
||||
}
|
||||
o.VotingDeadline = uint32(votingDeadline.Uint64())
|
||||
|
||||
investmentDeadline, err := items[18].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investmentDeadline: %w", err)
|
||||
}
|
||||
o.InvestmentDeadline = uint32(investmentDeadline.Uint64())
|
||||
|
||||
maturityDate, err := items[19].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid maturityDate: %w", err)
|
||||
}
|
||||
o.MaturityDate = uint32(maturityDate.Uint64())
|
||||
|
||||
proposalID, err := items[20].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid proposalID: %w", err)
|
||||
}
|
||||
o.ProposalID = proposalID.Uint64()
|
||||
|
||||
createdAt, err := items[21].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid createdAt: %w", err)
|
||||
}
|
||||
o.CreatedAt = uint32(createdAt.Uint64())
|
||||
|
||||
updatedAt, err := items[22].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid updatedAt: %w", err)
|
||||
}
|
||||
o.UpdatedAt = uint32(updatedAt.Uint64())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Investment represents an individual's investment in an opportunity.
|
||||
type Investment struct {
|
||||
ID uint64 // Unique investment ID
|
||||
OpportunityID uint64 // Opportunity invested in
|
||||
VitaID uint64 // Investor's Vita ID
|
||||
Investor util.Uint160 // Investor's address
|
||||
Amount uint64 // VTS amount invested
|
||||
Status InvestmentStatus // Current status
|
||||
ReturnAmount uint64 // Returns received (when completed)
|
||||
CreatedAt uint32 // Block height when invested
|
||||
UpdatedAt uint32 // Block height when last updated
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (i *Investment) DecodeBinary(br *io.BinReader) {
|
||||
i.ID = br.ReadU64LE()
|
||||
i.OpportunityID = br.ReadU64LE()
|
||||
i.VitaID = br.ReadU64LE()
|
||||
br.ReadBytes(i.Investor[:])
|
||||
i.Amount = br.ReadU64LE()
|
||||
i.Status = InvestmentStatus(br.ReadB())
|
||||
i.ReturnAmount = br.ReadU64LE()
|
||||
i.CreatedAt = br.ReadU32LE()
|
||||
i.UpdatedAt = br.ReadU32LE()
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (i *Investment) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(i.ID)
|
||||
bw.WriteU64LE(i.OpportunityID)
|
||||
bw.WriteU64LE(i.VitaID)
|
||||
bw.WriteBytes(i.Investor[:])
|
||||
bw.WriteU64LE(i.Amount)
|
||||
bw.WriteB(byte(i.Status))
|
||||
bw.WriteU64LE(i.ReturnAmount)
|
||||
bw.WriteU32LE(i.CreatedAt)
|
||||
bw.WriteU32LE(i.UpdatedAt)
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible interface.
|
||||
func (i *Investment) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.ID))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.OpportunityID))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.VitaID))),
|
||||
stackitem.NewByteArray(i.Investor.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.Amount))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.Status))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.ReturnAmount))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.CreatedAt))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(i.UpdatedAt))),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible interface.
|
||||
func (i *Investment) FromStackItem(item stackitem.Item) error {
|
||||
items, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not a struct")
|
||||
}
|
||||
if len(items) != 9 {
|
||||
return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
|
||||
}
|
||||
|
||||
id, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid id: %w", err)
|
||||
}
|
||||
i.ID = id.Uint64()
|
||||
|
||||
oppID, err := items[1].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid opportunityID: %w", err)
|
||||
}
|
||||
i.OpportunityID = oppID.Uint64()
|
||||
|
||||
vitaID, err := items[2].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid vitaID: %w", err)
|
||||
}
|
||||
i.VitaID = vitaID.Uint64()
|
||||
|
||||
investor, err := items[3].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investor: %w", err)
|
||||
}
|
||||
i.Investor, err = util.Uint160DecodeBytesBE(investor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investor address: %w", err)
|
||||
}
|
||||
|
||||
amount, err := items[4].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid amount: %w", err)
|
||||
}
|
||||
i.Amount = amount.Uint64()
|
||||
|
||||
status, err := items[5].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid status: %w", err)
|
||||
}
|
||||
i.Status = InvestmentStatus(status.Uint64())
|
||||
|
||||
returnAmount, err := items[6].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid returnAmount: %w", err)
|
||||
}
|
||||
i.ReturnAmount = returnAmount.Uint64()
|
||||
|
||||
createdAt, err := items[7].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid createdAt: %w", err)
|
||||
}
|
||||
i.CreatedAt = uint32(createdAt.Uint64())
|
||||
|
||||
updatedAt, err := items[8].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid updatedAt: %w", err)
|
||||
}
|
||||
i.UpdatedAt = uint32(updatedAt.Uint64())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InvestorEligibility represents an investor's eligibility status.
|
||||
type InvestorEligibility struct {
|
||||
VitaID uint64 // Investor's Vita ID
|
||||
Investor util.Uint160 // Investor's address
|
||||
Eligibility EligibilityType // Eligibility flags
|
||||
ScireCompleted bool // Investment education completed
|
||||
RiskScore uint8 // AI-calculated risk tolerance (1-10)
|
||||
TotalInvested uint64 // Total VTS currently invested
|
||||
TotalReturns uint64 // Total returns received
|
||||
ActiveInvestments uint64 // Number of active investments
|
||||
CompletedInvestments uint64 // Number of completed investments
|
||||
HasViolations bool // Any past investment abuse
|
||||
ViolationCount uint8 // Number of violations
|
||||
LastActivity uint32 // Block height of last activity
|
||||
CreatedAt uint32 // Block height when created
|
||||
UpdatedAt uint32 // Block height when updated
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (e *InvestorEligibility) DecodeBinary(br *io.BinReader) {
|
||||
e.VitaID = br.ReadU64LE()
|
||||
br.ReadBytes(e.Investor[:])
|
||||
e.Eligibility = EligibilityType(br.ReadB())
|
||||
e.ScireCompleted = br.ReadBool()
|
||||
e.RiskScore = br.ReadB()
|
||||
e.TotalInvested = br.ReadU64LE()
|
||||
e.TotalReturns = br.ReadU64LE()
|
||||
e.ActiveInvestments = br.ReadU64LE()
|
||||
e.CompletedInvestments = br.ReadU64LE()
|
||||
e.HasViolations = br.ReadBool()
|
||||
e.ViolationCount = br.ReadB()
|
||||
e.LastActivity = br.ReadU32LE()
|
||||
e.CreatedAt = br.ReadU32LE()
|
||||
e.UpdatedAt = br.ReadU32LE()
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (e *InvestorEligibility) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(e.VitaID)
|
||||
bw.WriteBytes(e.Investor[:])
|
||||
bw.WriteB(byte(e.Eligibility))
|
||||
bw.WriteBool(e.ScireCompleted)
|
||||
bw.WriteB(e.RiskScore)
|
||||
bw.WriteU64LE(e.TotalInvested)
|
||||
bw.WriteU64LE(e.TotalReturns)
|
||||
bw.WriteU64LE(e.ActiveInvestments)
|
||||
bw.WriteU64LE(e.CompletedInvestments)
|
||||
bw.WriteBool(e.HasViolations)
|
||||
bw.WriteB(e.ViolationCount)
|
||||
bw.WriteU32LE(e.LastActivity)
|
||||
bw.WriteU32LE(e.CreatedAt)
|
||||
bw.WriteU32LE(e.UpdatedAt)
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible interface.
|
||||
func (e *InvestorEligibility) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.VitaID))),
|
||||
stackitem.NewByteArray(e.Investor.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.Eligibility))),
|
||||
stackitem.NewBool(e.ScireCompleted),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.RiskScore))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.TotalInvested))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.TotalReturns))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.ActiveInvestments))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.CompletedInvestments))),
|
||||
stackitem.NewBool(e.HasViolations),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.ViolationCount))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.LastActivity))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.CreatedAt))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(e.UpdatedAt))),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible interface.
|
||||
func (e *InvestorEligibility) FromStackItem(item stackitem.Item) error {
|
||||
items, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not a struct")
|
||||
}
|
||||
if len(items) != 14 {
|
||||
return fmt.Errorf("wrong number of elements: expected 14, got %d", len(items))
|
||||
}
|
||||
|
||||
vitaID, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid vitaID: %w", err)
|
||||
}
|
||||
e.VitaID = vitaID.Uint64()
|
||||
|
||||
investor, err := items[1].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investor: %w", err)
|
||||
}
|
||||
e.Investor, err = util.Uint160DecodeBytesBE(investor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investor address: %w", err)
|
||||
}
|
||||
|
||||
eligibility, err := items[2].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid eligibility: %w", err)
|
||||
}
|
||||
e.Eligibility = EligibilityType(eligibility.Uint64())
|
||||
|
||||
scireCompleted, err := items[3].TryBool()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid scireCompleted: %w", err)
|
||||
}
|
||||
e.ScireCompleted = scireCompleted
|
||||
|
||||
riskScore, err := items[4].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid riskScore: %w", err)
|
||||
}
|
||||
e.RiskScore = uint8(riskScore.Uint64())
|
||||
|
||||
totalInvested, err := items[5].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid totalInvested: %w", err)
|
||||
}
|
||||
e.TotalInvested = totalInvested.Uint64()
|
||||
|
||||
totalReturns, err := items[6].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid totalReturns: %w", err)
|
||||
}
|
||||
e.TotalReturns = totalReturns.Uint64()
|
||||
|
||||
activeInvestments, err := items[7].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid activeInvestments: %w", err)
|
||||
}
|
||||
e.ActiveInvestments = activeInvestments.Uint64()
|
||||
|
||||
completedInvestments, err := items[8].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid completedInvestments: %w", err)
|
||||
}
|
||||
e.CompletedInvestments = completedInvestments.Uint64()
|
||||
|
||||
hasViolations, err := items[9].TryBool()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hasViolations: %w", err)
|
||||
}
|
||||
e.HasViolations = hasViolations
|
||||
|
||||
violationCount, err := items[10].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid violationCount: %w", err)
|
||||
}
|
||||
e.ViolationCount = uint8(violationCount.Uint64())
|
||||
|
||||
lastActivity, err := items[11].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid lastActivity: %w", err)
|
||||
}
|
||||
e.LastActivity = uint32(lastActivity.Uint64())
|
||||
|
||||
createdAt, err := items[12].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid createdAt: %w", err)
|
||||
}
|
||||
e.CreatedAt = uint32(createdAt.Uint64())
|
||||
|
||||
updatedAt, err := items[13].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid updatedAt: %w", err)
|
||||
}
|
||||
e.UpdatedAt = uint32(updatedAt.Uint64())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmploymentVerification represents verified employment for EIO eligibility.
|
||||
type EmploymentVerification struct {
|
||||
VitaID uint64 // Employee's Vita ID
|
||||
Employee util.Uint160 // Employee's address
|
||||
EmployerVitaID uint64 // Employer's Vita ID (organization)
|
||||
Employer util.Uint160 // Employer's address
|
||||
Position string // Job position/role
|
||||
StartDate uint32 // Block height when employment started
|
||||
EndDate uint32 // Block height when employment ended (0 = current)
|
||||
IsActive bool // Currently employed
|
||||
VerifiedAt uint32 // Block height when verified
|
||||
VerifiedBy util.Uint160 // Verifier's address (RoleInvestmentManager)
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (ev *EmploymentVerification) DecodeBinary(br *io.BinReader) {
|
||||
ev.VitaID = br.ReadU64LE()
|
||||
br.ReadBytes(ev.Employee[:])
|
||||
ev.EmployerVitaID = br.ReadU64LE()
|
||||
br.ReadBytes(ev.Employer[:])
|
||||
ev.Position = br.ReadString()
|
||||
ev.StartDate = br.ReadU32LE()
|
||||
ev.EndDate = br.ReadU32LE()
|
||||
ev.IsActive = br.ReadBool()
|
||||
ev.VerifiedAt = br.ReadU32LE()
|
||||
br.ReadBytes(ev.VerifiedBy[:])
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (ev *EmploymentVerification) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(ev.VitaID)
|
||||
bw.WriteBytes(ev.Employee[:])
|
||||
bw.WriteU64LE(ev.EmployerVitaID)
|
||||
bw.WriteBytes(ev.Employer[:])
|
||||
bw.WriteString(ev.Position)
|
||||
bw.WriteU32LE(ev.StartDate)
|
||||
bw.WriteU32LE(ev.EndDate)
|
||||
bw.WriteBool(ev.IsActive)
|
||||
bw.WriteU32LE(ev.VerifiedAt)
|
||||
bw.WriteBytes(ev.VerifiedBy[:])
|
||||
}
|
||||
|
||||
// ContractorVerification represents verified contractor status for CIO eligibility.
|
||||
type ContractorVerification struct {
|
||||
VitaID uint64 // Contractor's Vita ID
|
||||
Contractor util.Uint160 // Contractor's address
|
||||
PlatformID string // Platform identifier
|
||||
Platform util.Uint160 // Platform's address
|
||||
ContractorID string // Platform-specific contractor ID
|
||||
StartDate uint32 // Block height when started
|
||||
EndDate uint32 // Block height when ended (0 = current)
|
||||
IsActive bool // Currently active
|
||||
VerifiedAt uint32 // Block height when verified
|
||||
VerifiedBy util.Uint160 // Verifier's address
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (cv *ContractorVerification) DecodeBinary(br *io.BinReader) {
|
||||
cv.VitaID = br.ReadU64LE()
|
||||
br.ReadBytes(cv.Contractor[:])
|
||||
cv.PlatformID = br.ReadString()
|
||||
br.ReadBytes(cv.Platform[:])
|
||||
cv.ContractorID = br.ReadString()
|
||||
cv.StartDate = br.ReadU32LE()
|
||||
cv.EndDate = br.ReadU32LE()
|
||||
cv.IsActive = br.ReadBool()
|
||||
cv.VerifiedAt = br.ReadU32LE()
|
||||
br.ReadBytes(cv.VerifiedBy[:])
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (cv *ContractorVerification) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(cv.VitaID)
|
||||
bw.WriteBytes(cv.Contractor[:])
|
||||
bw.WriteString(cv.PlatformID)
|
||||
bw.WriteBytes(cv.Platform[:])
|
||||
bw.WriteString(cv.ContractorID)
|
||||
bw.WriteU32LE(cv.StartDate)
|
||||
bw.WriteU32LE(cv.EndDate)
|
||||
bw.WriteBool(cv.IsActive)
|
||||
bw.WriteU32LE(cv.VerifiedAt)
|
||||
bw.WriteBytes(cv.VerifiedBy[:])
|
||||
}
|
||||
|
||||
// InvestmentViolation represents a recorded investment abuse violation.
|
||||
type InvestmentViolation struct {
|
||||
ID uint64 // Unique violation ID
|
||||
VitaID uint64 // Violator's Vita ID
|
||||
Violator util.Uint160 // Violator's address
|
||||
OpportunityID uint64 // Related opportunity (0 = general)
|
||||
ViolationType string // Type of violation
|
||||
Description string // Violation description
|
||||
EvidenceHash util.Uint256 // Hash of evidence (off-chain)
|
||||
Penalty uint64 // VTS penalty applied
|
||||
ReportedBy util.Uint160 // Reporter's address
|
||||
ReportedAt uint32 // Block height when reported
|
||||
ResolvedAt uint32 // Block height when resolved (0 = pending)
|
||||
Resolution string // Resolution description
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (v *InvestmentViolation) DecodeBinary(br *io.BinReader) {
|
||||
v.ID = br.ReadU64LE()
|
||||
v.VitaID = br.ReadU64LE()
|
||||
br.ReadBytes(v.Violator[:])
|
||||
v.OpportunityID = br.ReadU64LE()
|
||||
v.ViolationType = br.ReadString()
|
||||
v.Description = br.ReadString()
|
||||
br.ReadBytes(v.EvidenceHash[:])
|
||||
v.Penalty = br.ReadU64LE()
|
||||
br.ReadBytes(v.ReportedBy[:])
|
||||
v.ReportedAt = br.ReadU32LE()
|
||||
v.ResolvedAt = br.ReadU32LE()
|
||||
v.Resolution = br.ReadString()
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (v *InvestmentViolation) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(v.ID)
|
||||
bw.WriteU64LE(v.VitaID)
|
||||
bw.WriteBytes(v.Violator[:])
|
||||
bw.WriteU64LE(v.OpportunityID)
|
||||
bw.WriteString(v.ViolationType)
|
||||
bw.WriteString(v.Description)
|
||||
bw.WriteBytes(v.EvidenceHash[:])
|
||||
bw.WriteU64LE(v.Penalty)
|
||||
bw.WriteBytes(v.ReportedBy[:])
|
||||
bw.WriteU32LE(v.ReportedAt)
|
||||
bw.WriteU32LE(v.ResolvedAt)
|
||||
bw.WriteString(v.Resolution)
|
||||
}
|
||||
|
||||
// CollocatioConfig represents configurable parameters for the investment contract.
|
||||
type CollocatioConfig struct {
|
||||
// Minimum requirements
|
||||
MinPIOParticipants uint64 // Default minimum for PIO (default: 100)
|
||||
MinEIOParticipants uint64 // Default minimum for EIO (default: 10)
|
||||
MinCIOParticipants uint64 // Default minimum for CIO (default: 25)
|
||||
|
||||
// Investment limits
|
||||
DefaultMinInvestment uint64 // Default minimum investment in VTS (default: 100)
|
||||
MaxIndividualCap uint64 // Maximum any individual can invest (default: 1000000)
|
||||
WealthConcentration uint64 // Max % of pool by single investor (basis points, default: 500 = 5%)
|
||||
|
||||
// Fees
|
||||
CreationFee uint64 // VTS fee to create opportunity
|
||||
InvestmentFee uint64 // Fee per investment (basis points, default: 50 = 0.5%)
|
||||
WithdrawalPenalty uint64 // Early withdrawal penalty (basis points, default: 200 = 2%)
|
||||
|
||||
// Timing
|
||||
MinVotingPeriod uint32 // Minimum blocks for voting (default: 10000)
|
||||
MinInvestmentPeriod uint32 // Minimum blocks for investment window (default: 20000)
|
||||
MinMaturityPeriod uint32 // Minimum blocks until maturity (default: 50000)
|
||||
|
||||
// Violation thresholds
|
||||
MaxViolationsBeforeBan uint8 // Violations before permanent ban (default: 3)
|
||||
ViolationCooldown uint32 // Blocks before violation expires (default: 1000000)
|
||||
}
|
||||
|
||||
// DecodeBinary implements the io.Serializable interface.
|
||||
func (c *CollocatioConfig) DecodeBinary(br *io.BinReader) {
|
||||
c.MinPIOParticipants = br.ReadU64LE()
|
||||
c.MinEIOParticipants = br.ReadU64LE()
|
||||
c.MinCIOParticipants = br.ReadU64LE()
|
||||
c.DefaultMinInvestment = br.ReadU64LE()
|
||||
c.MaxIndividualCap = br.ReadU64LE()
|
||||
c.WealthConcentration = br.ReadU64LE()
|
||||
c.CreationFee = br.ReadU64LE()
|
||||
c.InvestmentFee = br.ReadU64LE()
|
||||
c.WithdrawalPenalty = br.ReadU64LE()
|
||||
c.MinVotingPeriod = br.ReadU32LE()
|
||||
c.MinInvestmentPeriod = br.ReadU32LE()
|
||||
c.MinMaturityPeriod = br.ReadU32LE()
|
||||
c.MaxViolationsBeforeBan = br.ReadB()
|
||||
c.ViolationCooldown = br.ReadU32LE()
|
||||
}
|
||||
|
||||
// EncodeBinary implements the io.Serializable interface.
|
||||
func (c *CollocatioConfig) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteU64LE(c.MinPIOParticipants)
|
||||
bw.WriteU64LE(c.MinEIOParticipants)
|
||||
bw.WriteU64LE(c.MinCIOParticipants)
|
||||
bw.WriteU64LE(c.DefaultMinInvestment)
|
||||
bw.WriteU64LE(c.MaxIndividualCap)
|
||||
bw.WriteU64LE(c.WealthConcentration)
|
||||
bw.WriteU64LE(c.CreationFee)
|
||||
bw.WriteU64LE(c.InvestmentFee)
|
||||
bw.WriteU64LE(c.WithdrawalPenalty)
|
||||
bw.WriteU32LE(c.MinVotingPeriod)
|
||||
bw.WriteU32LE(c.MinInvestmentPeriod)
|
||||
bw.WriteU32LE(c.MinMaturityPeriod)
|
||||
bw.WriteB(c.MaxViolationsBeforeBan)
|
||||
bw.WriteU32LE(c.ViolationCooldown)
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible interface.
|
||||
func (c *CollocatioConfig) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinPIOParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinEIOParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinCIOParticipants))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultMinInvestment))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MaxIndividualCap))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.WealthConcentration))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.CreationFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.InvestmentFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.WithdrawalPenalty))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinVotingPeriod))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinInvestmentPeriod))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MinMaturityPeriod))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.MaxViolationsBeforeBan))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(c.ViolationCooldown))),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible interface.
|
||||
func (c *CollocatioConfig) FromStackItem(item stackitem.Item) error {
|
||||
items, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not a struct")
|
||||
}
|
||||
if len(items) != 14 {
|
||||
return fmt.Errorf("wrong number of elements: expected 14, got %d", len(items))
|
||||
}
|
||||
|
||||
minPIO, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minPIOParticipants: %w", err)
|
||||
}
|
||||
c.MinPIOParticipants = minPIO.Uint64()
|
||||
|
||||
minEIO, err := items[1].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minEIOParticipants: %w", err)
|
||||
}
|
||||
c.MinEIOParticipants = minEIO.Uint64()
|
||||
|
||||
minCIO, err := items[2].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minCIOParticipants: %w", err)
|
||||
}
|
||||
c.MinCIOParticipants = minCIO.Uint64()
|
||||
|
||||
defaultMin, err := items[3].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid defaultMinInvestment: %w", err)
|
||||
}
|
||||
c.DefaultMinInvestment = defaultMin.Uint64()
|
||||
|
||||
maxCap, err := items[4].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid maxIndividualCap: %w", err)
|
||||
}
|
||||
c.MaxIndividualCap = maxCap.Uint64()
|
||||
|
||||
wealthConc, err := items[5].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid wealthConcentration: %w", err)
|
||||
}
|
||||
c.WealthConcentration = wealthConc.Uint64()
|
||||
|
||||
creationFee, err := items[6].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid creationFee: %w", err)
|
||||
}
|
||||
c.CreationFee = creationFee.Uint64()
|
||||
|
||||
investmentFee, err := items[7].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid investmentFee: %w", err)
|
||||
}
|
||||
c.InvestmentFee = investmentFee.Uint64()
|
||||
|
||||
withdrawalPenalty, err := items[8].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid withdrawalPenalty: %w", err)
|
||||
}
|
||||
c.WithdrawalPenalty = withdrawalPenalty.Uint64()
|
||||
|
||||
minVoting, err := items[9].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minVotingPeriod: %w", err)
|
||||
}
|
||||
c.MinVotingPeriod = uint32(minVoting.Uint64())
|
||||
|
||||
minInvestment, err := items[10].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minInvestmentPeriod: %w", err)
|
||||
}
|
||||
c.MinInvestmentPeriod = uint32(minInvestment.Uint64())
|
||||
|
||||
minMaturity, err := items[11].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minMaturityPeriod: %w", err)
|
||||
}
|
||||
c.MinMaturityPeriod = uint32(minMaturity.Uint64())
|
||||
|
||||
maxViolations, err := items[12].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid maxViolationsBeforeBan: %w", err)
|
||||
}
|
||||
c.MaxViolationsBeforeBan = uint8(maxViolations.Uint64())
|
||||
|
||||
cooldown, err := items[13].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid violationCooldown: %w", err)
|
||||
}
|
||||
c.ViolationCooldown = uint32(cooldown.Uint64())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultCollocatioConfig returns the default configuration.
|
||||
func DefaultCollocatioConfig() CollocatioConfig {
|
||||
return CollocatioConfig{
|
||||
MinPIOParticipants: 100,
|
||||
MinEIOParticipants: 10,
|
||||
MinCIOParticipants: 25,
|
||||
DefaultMinInvestment: 100_00000000, // 100 VTS
|
||||
MaxIndividualCap: 1_000_000_00000000, // 1M VTS
|
||||
WealthConcentration: 500, // 5%
|
||||
CreationFee: 1000_00000000, // 1000 VTS
|
||||
InvestmentFee: 50, // 0.5%
|
||||
WithdrawalPenalty: 200, // 2%
|
||||
MinVotingPeriod: 10000, // ~10000 blocks
|
||||
MinInvestmentPeriod: 20000, // ~20000 blocks
|
||||
MinMaturityPeriod: 50000, // ~50000 blocks
|
||||
MaxViolationsBeforeBan: 3,
|
||||
ViolationCooldown: 1000000, // ~1M blocks
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue