Compare commits

..

No commits in common. "b63db20f341c80f1d7ad6319b9ae555c4dd173f5" and "00757090ff249beac6fb3626e98a11bf87abeee1" have entirely different histories.

24 changed files with 165 additions and 4394 deletions

View File

@ -1,224 +0,0 @@
# ADR-008 UI Implementation Guide
This document provides guidance for front-end developers implementing UI for the ADR-008 security features.
## 1. Commit-Reveal Investment System
### Overview
Investments now use a two-phase commit-reveal pattern to prevent front-running attacks. Users must first commit a hash of their investment, wait for a delay period, then reveal the actual amount.
### Flow
```
1. User decides to invest amount X in opportunity Y
2. Frontend generates random nonce (32 bytes)
3. Frontend computes commitment = SHA256(amount || nonce || investorAddress)
4. User calls commitInvestment(opportunityID, commitment, vitaID)
5. Wait 10+ blocks (configurable: CommitRevealDelay)
6. User calls revealInvestment(commitmentID, amount, nonce)
7. If valid, investment is executed
```
### Contract Methods
#### `commitInvestment(opportunityID: uint64, commitment: bytes32, vitaID: uint64) -> uint64`
- Returns: commitmentID
- Event: `CommitmentCreated(commitmentID, opportunityID, vitaID, revealDeadline)`
#### `revealInvestment(commitmentID: uint64, amount: uint64, nonce: bytes) -> bool`
- Must be called after CommitRevealDelay blocks (default: 10)
- Must be called before CommitRevealWindow expires (default: 1000 blocks)
- Event: `CommitmentRevealed(commitmentID, amount, investmentID)`
#### `cancelCommitment(commitmentID: uint64) -> bool`
- Can cancel pending commitments
- Event: `CommitmentCanceled(commitmentID)`
#### `getCommitment(commitmentID: uint64) -> CommitmentInfo | null`
- Returns commitment details including status
### Frontend Implementation Notes
```typescript
// Generate commitment
function createCommitment(amount: bigint, investorAddress: string): { commitment: string, nonce: string } {
const nonce = crypto.randomBytes(32);
const preimage = Buffer.concat([
Buffer.alloc(8), // amount as uint64 big-endian
nonce,
Buffer.from(investorAddress, 'hex')
]);
// Write amount as big-endian uint64
preimage.writeBigUInt64BE(amount, 0);
const commitment = sha256(preimage);
return {
commitment: commitment.toString('hex'),
nonce: nonce.toString('hex')
};
}
// IMPORTANT: Store nonce securely until reveal!
// If user loses nonce, they cannot reveal and funds are locked until expiry
```
### UI Considerations
- **Nonce Storage**: Store nonce in localStorage/sessionStorage with commitmentID as key
- **Progress Indicator**: Show blocks remaining until reveal is allowed
- **Deadline Warning**: Alert user if reveal deadline is approaching
- **Retry Logic**: If reveal fails, nonce is still valid for retry
---
## 2. Whale Concentration Limits
### Overview
No single investor can hold more than 5% (configurable) of any investment opportunity's total pool.
### How It Works
- When `invest()` is called, the contract checks:
```
(existingInvestment + newAmount) / (totalPool + newAmount) <= WealthConcentration
```
- Default limit: 500 basis points (5%)
- Enforced at investment time, not at commitment time
### UI Considerations
- **Pre-check**: Before committing, query user's existing investment in opportunity
- **Warning**: Show warning if user is approaching concentration limit
- **Error Handling**: Handle "investment would exceed whale concentration limit" error gracefully
### Querying Current Investment
```typescript
// Get all investments by an investor
const investments = await contract.getInvestmentsByInvestor(vitaID);
const investmentInOpp = investments.find(inv => inv.opportunityID === targetOppID);
const currentAmount = investmentInOpp?.amount || 0n;
// Get opportunity details
const opp = await contract.getOpportunity(oppID);
const totalPool = opp.totalPool;
// Calculate max additional investment
const maxConcentration = config.wealthConcentration; // e.g., 500 = 5%
const maxAllowed = (totalPool * BigInt(maxConcentration)) / 10000n;
const maxAdditional = maxAllowed - currentAmount;
```
---
## 3. Sybil Resistance Vesting
### Overview
New Vita tokens have a 30-day vesting period before gaining full rights. This prevents mass creation of fake identities for manipulation.
### Default Vesting Period
- 2,592,000 blocks (~30 days at 1 second per block)
- Committee can adjust vesting for individual tokens
### Contract Methods
#### `isFullyVested(tokenID: uint64) -> bool`
- Returns true if current block >= vestedUntil
#### `getVestingInfo(tokenID: uint64) -> [vestedUntil, isFullyVested, remainingBlocks] | null`
- `vestedUntil`: Block height when vesting completes
- `isFullyVested`: Current vesting status
- `remainingBlocks`: Blocks until fully vested (0 if already vested)
#### `setVesting(tokenID: uint64, vestedUntil: uint32) -> bool` (Committee only)
- Can accelerate vesting for verified identities
- Can extend vesting for suspicious accounts
### UI Considerations
#### Registration Flow
```typescript
// After Vita registration, show vesting status
const vestingInfo = await vita.getVestingInfo(tokenID);
if (!vestingInfo[1]) { // not fully vested
const remainingBlocks = vestingInfo[2];
const estimatedTime = remainingBlocks * 1; // 1 second per block
showVestingBanner(`Your identity will be fully verified in ~${formatDuration(estimatedTime)}`);
}
```
#### Feature Gating
Some features may require fully vested Vita:
- **Voting**: May require full vesting to prevent vote manipulation
- **Large Investments**: Higher limits may require vesting
- **Attestation**: May need vesting to attest others
```typescript
// Check before allowing sensitive actions
async function canPerformAction(vitaID: uint64, action: string): Promise<boolean> {
const isVested = await vita.isFullyVested(vitaID);
switch(action) {
case 'vote':
case 'large_investment':
case 'attest_others':
return isVested;
default:
return true; // Basic actions allowed during vesting
}
}
```
#### Vesting Progress Display
```typescript
// Calculate and display vesting progress
const vestingInfo = await vita.getVestingInfo(tokenID);
const vestedUntil = vestingInfo[0];
const token = await vita.getTokenByID(tokenID);
const createdAt = token.createdAt;
const totalVestingBlocks = vestedUntil - createdAt;
const elapsedBlocks = currentBlock - createdAt;
const progressPercent = Math.min(100, (elapsedBlocks / totalVestingBlocks) * 100);
```
---
## Configuration Values
These can be queried from the contracts:
### Collocatio Config
```typescript
const config = await collocatio.getConfig();
// config.commitRevealDelay = 10 (blocks)
// config.commitRevealWindow = 1000 (blocks)
// config.wealthConcentration = 500 (basis points = 5%)
```
### Vita Vesting
```typescript
// Default vesting period: 2,592,000 blocks (~30 days)
// Query individual token vesting via getVestingInfo()
```
---
## Events to Subscribe
### Collocatio Events
- `CommitmentCreated(commitmentID, opportunityID, vitaID, revealDeadline)`
- `CommitmentRevealed(commitmentID, amount, investmentID)`
- `CommitmentCanceled(commitmentID)`
### Vita Events
- `VestingUpdated(tokenID, vestedUntil, updatedBy)`
---
## Error Messages
| Error | Meaning | UI Action |
|-------|---------|-----------|
| `commitment already exists` | Duplicate commitment | Show existing commitment |
| `commitment not found` | Invalid commitmentID | Check ID is correct |
| `reveal too early` | Before delay period | Show countdown |
| `reveal window expired` | Past deadline | Commitment forfeited |
| `commitment verification failed` | Wrong amount/nonce | Check stored values |
| `investment would exceed whale concentration limit` | Too much in one opp | Show max allowed |
| `vita is not fully vested` | Vesting incomplete | Show remaining time |

View File

@ -220,67 +220,64 @@ func Init(t *testing.T, rootpath string, e *tutustest.Executor) {
// Block #13: add `.com` root to NNS.
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
// Block #14: add `.one` root to NNS.
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "one") // block #14
// Block #15: register `tutus.one` via NNS.
// Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
"tutus.one", priv0ScriptHash) // block #15
"neo.com", priv0ScriptHash) // block #14
res := e.GetTxExecResult(t, registerTxH)
require.Equal(t, 1, len(res.Events)) // transfer
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #16: set A record type with priv0 owner via NNS.
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "tutus.one", int64(nns.A), "1.2.3.4") // block #16
// Block #15: set A record type with priv0 owner via NNS.
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
// Block #17: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", RublesNewTestvalue) // tx1
// Invoke `test_contract.go`: put values to check `findstates` RPC call.
txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2
txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3
txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4
e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #17
e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16
e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true))
// Block #18: deploy NeoFS Object contract (NEP11-Divisible).
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
nfsPath := filepath.Join(examplesPrefix, "nft-d")
nfsConfigPath := filepath.Join(nfsPath, "nft.yml")
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, NFSOContractID) // block #18
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, NFSOContractID) // block #17
nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0)
nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1)
// Block #19: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
containerID := util.Uint256{1, 2, 3}
objectID := util.Uint256{4, 5, 6}
txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer",
priv0ScriptHash, nfsHash, 10_0000_0000, []any{containerID.BytesBE(), objectID.BytesBE()}) // block #19
priv0ScriptHash, nfsHash, 10_0000_0000, []any{containerID.BytesBE(), objectID.BytesBE()}) // block #18
res = e.GetTxExecResult(t, txGas0toNFSH)
require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer
tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #20: transfer 0.25 NFSO from priv0 to priv1.
nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #20
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19
// Block #21: transfer 1000 GAS to priv1.
// Block #20: transfer 1000 GAS to priv1.
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(),
priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #21
priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20
// Block #22: transfer 0.05 NFSO from priv1 back to priv0.
nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #22
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21
// Block #23: deploy storage_contract (Storage contract for `traverseiterator` and `terminatesession` RPC calls test).
// Block #22: deploy storage_contract (Storage contract for `traverseiterator` and `terminatesession` RPC calls test).
storagePath := filepath.Join(testDataPrefix, "storage", "storage_contract.go")
storageCfg := filepath.Join(testDataPrefix, "storage", "storage_contract.yml")
_, _, _ = deployContractFromPriv0(t, storagePath, "Storage", storageCfg, StorageContractID)
// Block #24: add FAULTed transaction to check WSClient waitloops.
// Block #23: add FAULTed transaction to check WSClient waitloops.
faultedTx := e.PrepareInvocationNoSign(t, []byte{byte(opcode.ABORT)}, e.Chain.BlockHeight()+2) // use larger VUB for TestClient_Wait.
e.SignTx(t, faultedTx, -1, acc0)
e.AddNewBlock(t, faultedTx)
@ -298,9 +295,9 @@ func Init(t *testing.T, rootpath string, e *tutustest.Executor) {
txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
sr21, err := e.Chain.GetStateModule().GetStateRoot(21)
sr20, err := e.Chain.GetStateModule().GetStateRoot(20)
require.NoError(t, err)
t.Logf("Block #21 stateroot LE: %s", sr21.Root.StringLE())
t.Logf("Block #20 stateroot LE: %s", sr20.Root.StringLE())
}
func newDeployTx(t *testing.T, e *tutustest.Executor, sender tutustest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) {

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@ import (
"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/core/storage"
"github.com/tutus-one/tutus-chain/pkg/crypto/hash"
"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"
@ -54,10 +53,6 @@ const (
collocatioPrefixEmploymentByEmployer byte = 0x51 // employerVitaID + employeeVitaID -> exists
collocatioPrefixContractor byte = 0x60 // contractorVitaID -> ContractorVerification
collocatioPrefixContractorByPlatform byte = 0x61 // platformHash + contractorVitaID -> exists
collocatioPrefixCommitment byte = 0x70 // commitmentID -> InvestmentCommitment
collocatioPrefixCommitmentByOpp byte = 0x71 // opportunityID + commitmentID -> exists
collocatioPrefixCommitmentByInvestor byte = 0x72 // vitaID + commitmentID -> exists
collocatioPrefixCommitmentCounter byte = 0x7F // -> next commitment ID
)
// Collocatio events.
@ -78,9 +73,6 @@ const (
collocatioEmploymentRevokedEvent = "EmploymentRevoked"
collocatioContractorVerifiedEvent = "ContractorVerified"
collocatioContractorRevokedEvent = "ContractorRevoked"
collocatioCommitmentCreatedEvent = "CommitmentCreated"
collocatioCommitmentRevealedEvent = "CommitmentRevealed"
collocatioCommitmentCanceledEvent = "CommitmentCanceled"
)
// RoleInvestmentManager is the role ID for investment management.
@ -101,8 +93,6 @@ const (
defaultMinInvestmentPeriod uint32 = 20000
defaultMinMaturityPeriod uint32 = 50000
defaultMaxViolationsBeforeBan uint8 = 3
defaultCommitRevealDelay uint32 = 10 // Min blocks between commit and reveal
defaultCommitRevealWindow uint32 = 1000 // Max blocks to reveal after delay
)
var _ interop.Contract = (*Collocatio)(nil)
@ -311,38 +301,6 @@ func newCollocatio() *Collocatio {
md = NewMethodAndPrice(c.getOpportunitiesByStatus, 1<<16, callflag.ReadStates)
c.AddMethod(md, desc)
// commitInvestment - Phase 1 of commit-reveal (anti-front-running)
desc = NewDescriptor("commitInvestment", smartcontract.IntegerType,
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
manifest.NewParameter("commitment", smartcontract.Hash256Type))
md = NewMethodAndPrice(c.commitInvestment, 1<<16, callflag.States|callflag.AllowNotify)
c.AddMethod(md, desc)
// revealInvestment - Phase 2 of commit-reveal
desc = NewDescriptor("revealInvestment", smartcontract.IntegerType,
manifest.NewParameter("commitmentID", smartcontract.IntegerType),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("nonce", smartcontract.ByteArrayType))
md = NewMethodAndPrice(c.revealInvestment, 1<<17, callflag.States|callflag.AllowNotify)
c.AddMethod(md, desc)
// cancelCommitment - Cancel a pending commitment
desc = NewDescriptor("cancelCommitment", smartcontract.BoolType,
manifest.NewParameter("commitmentID", smartcontract.IntegerType))
md = NewMethodAndPrice(c.cancelCommitment, 1<<16, callflag.States|callflag.AllowNotify)
c.AddMethod(md, desc)
// getCommitment - Query commitment details
desc = NewDescriptor("getCommitment", smartcontract.ArrayType,
manifest.NewParameter("commitmentID", smartcontract.IntegerType))
md = NewMethodAndPrice(c.getCommitment, 1<<15, callflag.ReadStates)
c.AddMethod(md, desc)
// getCommitmentCount
desc = NewDescriptor("getCommitmentCount", smartcontract.IntegerType)
md = NewMethodAndPrice(c.getCommitmentCount, 1<<15, callflag.ReadStates)
c.AddMethod(md, desc)
// ===== Events =====
eDesc := NewEventDescriptor(collocatioOpportunityCreatedEvent,
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
@ -426,22 +384,6 @@ func newCollocatio() *Collocatio {
manifest.NewParameter("platform", smartcontract.Hash160Type))
c.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(collocatioCommitmentCreatedEvent,
manifest.NewParameter("commitmentID", smartcontract.IntegerType),
manifest.NewParameter("opportunityID", smartcontract.IntegerType),
manifest.NewParameter("investor", smartcontract.Hash160Type))
c.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(collocatioCommitmentRevealedEvent,
manifest.NewParameter("commitmentID", smartcontract.IntegerType),
manifest.NewParameter("investmentID", smartcontract.IntegerType),
manifest.NewParameter("amount", smartcontract.IntegerType))
c.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(collocatioCommitmentCanceledEvent,
manifest.NewParameter("commitmentID", smartcontract.IntegerType))
c.AddEvent(NewEvent(eDesc))
return c
}
@ -472,8 +414,6 @@ func (c *Collocatio) Initialize(ic *interop.Context, hf *config.Hardfork, newMD
MinMaturityPeriod: defaultMinMaturityPeriod,
MaxViolationsBeforeBan: defaultMaxViolationsBeforeBan,
ViolationCooldown: 1000000,
CommitRevealDelay: defaultCommitRevealDelay,
CommitRevealWindow: defaultCommitRevealWindow,
}
c.setConfigInternal(ic.DAO, &cfg)
@ -740,33 +680,6 @@ func makeCollocatioContractorByPlatformKey(platform util.Uint160, contractorVita
return key
}
func makeCollocatioCommitmentKey(commitmentID uint64) []byte {
key := make([]byte, 9)
key[0] = collocatioPrefixCommitment
binary.BigEndian.PutUint64(key[1:], commitmentID)
return key
}
func makeCollocatioCommitmentByOppKey(oppID, commitmentID uint64) []byte {
key := make([]byte, 17)
key[0] = collocatioPrefixCommitmentByOpp
binary.BigEndian.PutUint64(key[1:9], oppID)
binary.BigEndian.PutUint64(key[9:], commitmentID)
return key
}
func makeCollocatioCommitmentByInvestorKey(vitaID, commitmentID uint64) []byte {
key := make([]byte, 17)
key[0] = collocatioPrefixCommitmentByInvestor
binary.BigEndian.PutUint64(key[1:9], vitaID)
binary.BigEndian.PutUint64(key[9:], commitmentID)
return key
}
func makeCollocatioCommitmentCounterKey() []byte {
return []byte{collocatioPrefixCommitmentCounter}
}
// ============================================================================
// Internal Storage Methods
// ============================================================================
@ -884,43 +797,6 @@ func (c *Collocatio) putEligibility(d *dao.Simple, elig *state.InvestorEligibili
d.PutStorageItem(c.ID, makeCollocatioEligByOwnerKey(elig.Investor), buf)
}
func (c *Collocatio) getCommitmentInternal(d *dao.Simple, commitmentID uint64) *state.InvestmentCommitment {
si := d.GetStorageItem(c.ID, makeCollocatioCommitmentKey(commitmentID))
if si == nil {
return nil
}
commitment := new(state.InvestmentCommitment)
item, _ := stackitem.Deserialize(si)
commitment.FromStackItem(item)
return commitment
}
func (c *Collocatio) putCommitment(d *dao.Simple, commitment *state.InvestmentCommitment) {
item, _ := commitment.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(c.ID, makeCollocatioCommitmentKey(commitment.ID), data)
}
// getInvestorTotalInOpportunity returns the total amount an investor has invested in a specific opportunity.
func (c *Collocatio) getInvestorTotalInOpportunity(d *dao.Simple, vitaID, oppID uint64) uint64 {
prefix := []byte{collocatioPrefixInvestmentByInvestor}
prefix = append(prefix, make([]byte, 8)...)
binary.BigEndian.PutUint64(prefix[1:], vitaID)
var total uint64
d.Seek(c.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
if len(k) >= 16 {
invID := binary.BigEndian.Uint64(k[8:16])
inv := c.getInvestmentInternal(d, invID)
if inv != nil && inv.OpportunityID == oppID && inv.Status == state.InvestmentActive {
total += inv.Amount
}
}
return true
})
return total
}
// ============================================================================
// Contract Methods
// ============================================================================
@ -1140,21 +1016,6 @@ func (c *Collocatio) invest(ic *interop.Context, args []stackitem.Item) stackite
// Calculate fee
cfg := c.getConfigInternal(ic.DAO)
// Whale concentration check - prevent any single investor from holding too much of the pool
if cfg.WealthConcentration > 0 {
existingInvestment := c.getInvestorTotalInOpportunity(ic.DAO, vitaID, oppID)
newTotal := existingInvestment + amount
// Calculate what percentage of the pool this investor would hold
// (newTotal / (opp.TotalPool + amount)) * 10000 > WealthConcentration
futurePool := opp.TotalPool + amount
if futurePool > 0 {
concentration := (newTotal * 10000) / futurePool
if concentration > cfg.WealthConcentration {
panic("investment would exceed whale concentration limit")
}
}
}
fee := (amount * cfg.InvestmentFee) / 10000
netAmount := amount - fee
@ -2315,300 +2176,3 @@ func contractorToStackItem(cv *state.ContractorVerification) stackitem.Item {
stackitem.NewByteArray(cv.VerifiedBy.BytesBE()),
})
}
func commitmentToStackItem(c *state.InvestmentCommitment) stackitem.Item {
return stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(new(big.Int).SetUint64(c.ID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(c.OpportunityID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(c.VitaID)),
stackitem.NewByteArray(c.Investor.BytesBE()),
stackitem.NewByteArray(c.Commitment.BytesBE()),
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(c.Status))),
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(c.CommittedAt))),
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(c.RevealDeadline))),
stackitem.NewBigInteger(new(big.Int).SetUint64(c.RevealedAmount)),
stackitem.NewBigInteger(new(big.Int).SetUint64(c.InvestmentID)),
})
}
// ============================================================================
// Commit-Reveal System (Anti-Front-Running)
// ============================================================================
// commitInvestment creates a commitment to invest without revealing the amount.
// The commitment is hash(amount || nonce || investor).
func (col *Collocatio) commitInvestment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
oppID := toUint64(args[0])
commitmentHashBytes, err := args[1].TryBytes()
if err != nil {
panic(err)
}
commitmentHash, err := util.Uint256DecodeBytesBE(commitmentHashBytes)
if err != nil {
panic("invalid commitment hash")
}
caller := ic.VM.GetCallingScriptHash()
// Validate caller has Vita
vita, err := col.Vita.GetTokenByOwner(ic.DAO, caller)
if err != nil {
panic("caller must have Vita token")
}
vitaID := vita.TokenID
// Get opportunity
opp := col.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")
}
// Check eligibility
if !col.isEligibleInternal(ic.DAO, caller, opp.Type) {
panic("investor not eligible for this opportunity type")
}
cfg := col.getConfigInternal(ic.DAO)
// Create commitment
commitmentID := col.incrementCounter(ic.DAO, makeCollocatioCommitmentCounterKey())
currentBlock := ic.Block.Index
commitment := &state.InvestmentCommitment{
ID: commitmentID,
OpportunityID: oppID,
VitaID: vitaID,
Investor: caller,
Commitment: commitmentHash,
Status: state.CommitmentPending,
CommittedAt: currentBlock,
RevealDeadline: currentBlock + cfg.CommitRevealDelay + cfg.CommitRevealWindow,
RevealedAmount: 0,
InvestmentID: 0,
}
col.putCommitment(ic.DAO, commitment)
// Store indexes
ic.DAO.PutStorageItem(col.ID, makeCollocatioCommitmentByOppKey(oppID, commitmentID), []byte{1})
ic.DAO.PutStorageItem(col.ID, makeCollocatioCommitmentByInvestorKey(vitaID, commitmentID), []byte{1})
// Emit event
ic.AddNotification(col.Hash, collocatioCommitmentCreatedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(new(big.Int).SetUint64(commitmentID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(oppID)),
stackitem.NewByteArray(caller.BytesBE()),
}))
return stackitem.NewBigInteger(new(big.Int).SetUint64(commitmentID))
}
// revealInvestment reveals the investment amount and executes the investment.
// The reveal must happen after CommitRevealDelay blocks but before RevealDeadline.
func (col *Collocatio) revealInvestment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
commitmentID := toUint64(args[0])
amount := toUint64(args[1])
nonce, err := args[2].TryBytes()
if err != nil {
panic("invalid nonce")
}
caller := ic.VM.GetCallingScriptHash()
// Get commitment
commitment := col.getCommitmentInternal(ic.DAO, commitmentID)
if commitment == nil {
panic("commitment not found")
}
if commitment.Investor != caller {
panic("only commitment owner can reveal")
}
if commitment.Status != state.CommitmentPending {
panic("commitment already processed")
}
cfg := col.getConfigInternal(ic.DAO)
// Check timing
currentBlock := ic.Block.Index
revealStart := commitment.CommittedAt + cfg.CommitRevealDelay
if currentBlock < revealStart {
panic("reveal period not started yet")
}
if currentBlock > commitment.RevealDeadline {
panic("reveal deadline passed")
}
// Verify commitment: hash(amount || nonce || investor)
preimage := make([]byte, 8+len(nonce)+util.Uint160Size)
binary.BigEndian.PutUint64(preimage[:8], amount)
copy(preimage[8:8+len(nonce)], nonce)
copy(preimage[8+len(nonce):], caller.BytesBE())
// Hash the preimage using SHA256
computedHash := hash.Sha256(preimage)
if computedHash != commitment.Commitment {
panic("commitment verification failed")
}
// Get opportunity
opp := col.getOpportunityInternal(ic.DAO, commitment.OpportunityID)
if opp == nil {
panic("opportunity not found")
}
if opp.Status != state.OpportunityActive {
panic("opportunity is not active")
}
if currentBlock > opp.InvestmentDeadline {
panic("investment deadline has passed")
}
// Validate amount
if amount < opp.MinInvestment {
panic("investment below minimum")
}
if amount > opp.MaxInvestment {
panic("investment exceeds maximum")
}
if opp.MaxParticipants > 0 && opp.CurrentParticipants >= opp.MaxParticipants {
panic("maximum participants reached")
}
// Whale concentration check
if cfg.WealthConcentration > 0 {
existingInvestment := col.getInvestorTotalInOpportunity(ic.DAO, commitment.VitaID, commitment.OpportunityID)
newTotal := existingInvestment + amount
futurePool := opp.TotalPool + amount
if futurePool > 0 {
concentration := (newTotal * 10000) / futurePool
if concentration > cfg.WealthConcentration {
panic("investment would exceed whale concentration limit")
}
}
}
// Calculate fee
fee := (amount * cfg.InvestmentFee) / 10000
netAmount := amount - fee
// Transfer VTS from investor
if err := col.VTS.transferUnrestricted(ic, caller, col.Hash, new(big.Int).SetUint64(amount), nil); err != nil {
panic("failed to transfer investment amount")
}
// Send fee to Treasury
if fee > 0 {
if err := col.VTS.transferUnrestricted(ic, col.Hash, nativehashes.Treasury, new(big.Int).SetUint64(fee), nil); err != nil {
panic("failed to transfer fee to treasury")
}
}
// Create investment record
invID := col.incrementCounter(ic.DAO, makeCollocatioInvCounterKey())
inv := &state.Investment{
ID: invID,
OpportunityID: commitment.OpportunityID,
VitaID: commitment.VitaID,
Investor: caller,
Amount: netAmount,
Status: state.InvestmentActive,
ReturnAmount: 0,
CreatedAt: currentBlock,
UpdatedAt: currentBlock,
}
col.putInvestment(ic.DAO, inv)
// Store indexes
ic.DAO.PutStorageItem(col.ID, makeCollocatioInvByOppKey(commitment.OpportunityID, invID), []byte{1})
ic.DAO.PutStorageItem(col.ID, makeCollocatioInvByInvestorKey(commitment.VitaID, invID), []byte{1})
// Update opportunity
opp.CurrentParticipants++
opp.TotalPool += netAmount
opp.UpdatedAt = currentBlock
col.putOpportunity(ic.DAO, opp)
// Update commitment
commitment.Status = state.CommitmentRevealed
commitment.RevealedAmount = amount
commitment.InvestmentID = invID
col.putCommitment(ic.DAO, commitment)
// Update eligibility
col.updateEligibilityOnInvest(ic.DAO, caller, commitment.VitaID, netAmount, currentBlock)
// Emit events
ic.AddNotification(col.Hash, collocatioCommitmentRevealedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(new(big.Int).SetUint64(commitmentID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(invID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(amount)),
}))
ic.AddNotification(col.Hash, collocatioInvestmentMadeEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(new(big.Int).SetUint64(invID)),
stackitem.NewBigInteger(new(big.Int).SetUint64(commitment.OpportunityID)),
stackitem.NewByteArray(caller.BytesBE()),
stackitem.NewBigInteger(new(big.Int).SetUint64(netAmount)),
}))
return stackitem.NewBigInteger(new(big.Int).SetUint64(invID))
}
// cancelCommitment cancels a pending commitment.
func (col *Collocatio) cancelCommitment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
commitmentID := toUint64(args[0])
caller := ic.VM.GetCallingScriptHash()
commitment := col.getCommitmentInternal(ic.DAO, commitmentID)
if commitment == nil {
panic("commitment not found")
}
if commitment.Investor != caller {
panic("only commitment owner can cancel")
}
if commitment.Status != state.CommitmentPending {
panic("commitment already processed")
}
// Update commitment status
commitment.Status = state.CommitmentCanceled
col.putCommitment(ic.DAO, commitment)
// Emit event
ic.AddNotification(col.Hash, collocatioCommitmentCanceledEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(new(big.Int).SetUint64(commitmentID)),
}))
return stackitem.NewBool(true)
}
// getCommitment returns commitment details.
func (col *Collocatio) getCommitment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
commitmentID := toUint64(args[0])
commitment := col.getCommitmentInternal(ic.DAO, commitmentID)
if commitment == nil {
return stackitem.Null{}
}
return commitmentToStackItem(commitment)
}
// getCommitmentCount returns the total number of commitments.
func (col *Collocatio) getCommitmentCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
count := col.getCounter(ic.DAO, makeCollocatioCommitmentCounterKey())
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}

View File

@ -124,10 +124,6 @@ type (
IsAdultVerified(d *dao.Simple, owner util.Uint160) bool
// GetTotalTokenCount returns total number of Vita tokens (for quorum calculation).
GetTotalTokenCount(d *dao.Simple) uint64
// ExistsInternal checks if a Vita token with the given ID exists.
ExistsInternal(d *dao.Simple, vitaID uint64) bool
// OwnerOfInternal returns the owner of a Vita token by ID.
OwnerOfInternal(d *dao.Simple, vitaID uint64) util.Uint160
}
// IRoleRegistry is an interface required from native RoleRegistry contract
@ -335,19 +331,6 @@ type (
// Address returns the contract's script hash.
Address() util.Uint160
}
// IAncora is an interface required from native Ancora contract for
// interaction with Blockchain and other native contracts.
// Ancora anchors Merkle roots of off-chain data for privacy-preserving verification.
IAncora interface {
interop.Contract
// VerifyProofInternal verifies a Merkle proof against the stored root.
VerifyProofInternal(d *dao.Simple, vitaID uint64, dataType state.DataType, leaf []byte, proof [][]byte, index uint64) bool
// RequireValidRoot panics if no valid root exists for the vitaID and dataType.
RequireValidRoot(d *dao.Simple, vitaID uint64, dataType state.DataType)
// Address returns the contract's script hash.
Address() util.Uint160
}
)
// Contracts is a convenient wrapper around an arbitrary set of native contracts
@ -547,12 +530,6 @@ func (cs *Contracts) Annos() IAnnos {
return cs.ByName(nativenames.Annos).(IAnnos)
}
// Ancora returns native IAncora contract implementation. It panics if
// there's no contract with proper name in cs.
func (cs *Contracts) Ancora() IAncora {
return cs.ByName(nativenames.Ancora).(IAncora)
}
// NewDefaultContracts returns a new set of default native contracts.
func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
mgmt := NewManagement()
@ -722,11 +699,6 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
// Wire Annos into Eligere for voting age verification
eligere.Annos = annos
// Create Ancora (Merkle Root Anchoring) contract
ancora := newAncora()
ancora.Vita = vita
ancora.Tutus = tutus
return []interop.Contract{
mgmt,
s,
@ -754,6 +726,5 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
pons,
collocatio,
annos,
ancora,
}
}

View File

@ -1,282 +0,0 @@
package native_test
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/stretchr/testify/require"
"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/tutustest"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
func newAncoraClient(t *testing.T) *tutustest.ContractInvoker {
return newNativeClient(t, nativenames.Ancora)
}
// TestAncora_GetConfig tests the getConfig method.
func TestAncora_GetConfig(t *testing.T) {
c := newAncoraClient(t)
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
arr, ok := stack[0].Value().([]stackitem.Item)
require.True(t, ok, "expected array result")
require.Equal(t, 7, len(arr)) // StateAnchorsConfig has 7 fields
// Check default values
defaultAlgo, _ := arr[0].TryInteger()
require.Equal(t, int64(state.TreeAlgorithmSHA256), defaultAlgo.Int64())
maxProofDepth, _ := arr[1].TryInteger()
require.Equal(t, int64(32), maxProofDepth.Int64())
defaultMaxUpdates, _ := arr[2].TryInteger()
require.Equal(t, int64(10), defaultMaxUpdates.Int64())
defaultCooldown, _ := arr[3].TryInteger()
require.Equal(t, int64(1), defaultCooldown.Int64())
maxHistory, _ := arr[4].TryInteger()
require.Equal(t, int64(100), maxHistory.Int64())
erasureGrace, _ := arr[5].TryInteger()
require.Equal(t, int64(1000), erasureGrace.Int64())
attestValid, _ := arr[6].TryInteger()
require.Equal(t, int64(86400), attestValid.Int64())
}, "getConfig")
}
// TestAncora_GetDataRoot_NonExistent tests getting a non-existent data root.
func TestAncora_GetDataRoot_NonExistent(t *testing.T) {
c := newAncoraClient(t)
// Non-existent root should return null
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
require.Nil(t, stack[0].Value(), "expected null for non-existent root")
}, "getDataRoot", uint64(999), uint32(state.DataTypeMedical))
}
// TestAncora_GetProvider_NonExistent tests getting a non-existent provider config.
func TestAncora_GetProvider_NonExistent(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
// Non-existent provider should return null
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
require.Nil(t, stack[0].Value(), "expected null for non-existent provider")
}, "getProvider", uint32(state.DataTypeMedical), acc.ScriptHash())
}
// TestAncora_GetErasureInfo_NonExistent tests getting non-existent erasure info.
func TestAncora_GetErasureInfo_NonExistent(t *testing.T) {
c := newAncoraClient(t)
// Non-existent erasure info should return null
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
require.Nil(t, stack[0].Value(), "expected null for non-existent erasure info")
}, "getErasureInfo", uint64(999), uint32(state.DataTypeMedical))
}
// TestAncora_IsProviderActive_NonExistent tests checking non-existent provider.
func TestAncora_IsProviderActive_NonExistent(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
// Non-existent provider should return false
c.Invoke(t, false, "isProviderActive", uint32(state.DataTypeMedical), acc.ScriptHash())
}
// TestAncora_RegisterProvider_NoCommittee tests that non-committee cannot register providers.
func TestAncora_RegisterProvider_NoCommittee(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
// registerProvider takes: dataType, provider, description, maxUpdatesPerBlock, updateCooldown
randomInvoker.InvokeFail(t, "invalid committee signature",
"registerProvider",
uint32(state.DataTypeMedical),
acc.ScriptHash(),
"Test Healthcare Provider",
uint32(10),
uint32(1),
)
}
// TestAncora_RegisterProvider_CommitteeSuccess tests committee can register providers.
func TestAncora_RegisterProvider_CommitteeSuccess(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
providerAcc := e.NewAccount(t)
// Register a healthcare provider (c is already a CommitteeInvoker)
c.Invoke(t, true,
"registerProvider",
uint32(state.DataTypeMedical),
providerAcc.ScriptHash(),
"Test Healthcare Provider",
uint32(10),
uint32(1),
)
// Verify provider is active
c.Invoke(t, true, "isProviderActive", uint32(state.DataTypeMedical), providerAcc.ScriptHash())
// Verify provider info
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
arr, ok := stack[0].Value().([]stackitem.Item)
require.True(t, ok, "expected array result")
require.Equal(t, 7, len(arr)) // ProviderConfig has 7 fields
// Check dataType
dataType, _ := arr[0].TryInteger()
require.Equal(t, int64(state.DataTypeMedical), dataType.Int64())
// Check active status
active, _ := arr[4].TryBool()
require.True(t, active)
}, "getProvider", uint32(state.DataTypeMedical), providerAcc.ScriptHash())
}
// TestAncora_RevokeProvider_NoCommittee tests that non-committee cannot revoke providers.
func TestAncora_RevokeProvider_NoCommittee(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
randomInvoker.InvokeFail(t, "invalid committee signature",
"revokeProvider",
uint32(state.DataTypeMedical),
acc.ScriptHash(),
)
}
// TestAncora_UpdateDataRoot_VitaNotFound tests that updating root for non-existent Vita fails.
// The contract should fail when the Vita doesn't exist.
func TestAncora_UpdateDataRoot_VitaNotFound(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
root := hash.Sha256([]byte("test data")).BytesBE()
// Should fail - when calling updateDataRoot with invalid Vita ID
// The error could be "vita not found" or "unauthorized" depending on check order
randomInvoker.InvokeFail(t, "",
"updateDataRoot",
uint64(999),
uint32(state.DataTypeMedical),
root,
uint64(100),
uint32(state.TreeAlgorithmSHA256),
"1.0.0",
[]byte{},
)
}
// Note: TestAncora_VerifyProof tests would require complex array serialization
// that the test framework doesn't support directly. Proof verification is tested
// via cross-contract integration tests.
// TestAncora_RequestErasure_Unauthorized tests that erasure request requires Vita ownership.
// When Vita doesn't exist, OwnerOfInternal returns empty Uint160 and the caller
// fails the witness check, resulting in "unauthorized" error.
func TestAncora_RequestErasure_Unauthorized(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
// Should fail - caller is not the Vita owner (Vita doesn't exist)
randomInvoker.InvokeFail(t, "unauthorized",
"requestErasure",
uint64(999),
uint32(state.DataTypeMedical),
"GDPR Art. 17",
)
}
// TestAncora_ConfirmErasure_Unauthorized tests that unauthorized providers cannot confirm erasure.
func TestAncora_ConfirmErasure_Unauthorized(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
randomInvoker.InvokeFail(t, "unauthorized: caller is not authorized provider",
"confirmErasure",
uint64(1),
uint32(state.DataTypeMedical),
)
}
// TestAncora_DenyErasure_NoPending tests that denying non-existent erasure fails.
func TestAncora_DenyErasure_NoPending(t *testing.T) {
c := newAncoraClient(t)
e := c.Executor
acc := e.NewAccount(t)
randomInvoker := c.WithSigners(acc)
randomInvoker.InvokeFail(t, "no pending erasure request",
"denyErasure",
uint64(1),
uint32(state.DataTypeMedical),
"Legal hold",
)
}
// TestAncora_VerifyPortabilityAttestation_Invalid tests verification of invalid attestation.
func TestAncora_VerifyPortabilityAttestation_Invalid(t *testing.T) {
c := newAncoraClient(t)
// Invalid attestation should return false
c.Invoke(t, false, "verifyPortabilityAttestation", []byte("invalid attestation"))
}
// TestAncora_DataTypes tests that all data types are valid.
func TestAncora_DataTypes(t *testing.T) {
// Verify data type constants
require.Equal(t, state.DataType(0), state.DataTypeMedical)
require.Equal(t, state.DataType(1), state.DataTypeEducation)
require.Equal(t, state.DataType(2), state.DataTypeInvestment)
require.Equal(t, state.DataType(3), state.DataTypeDocuments)
require.Equal(t, state.DataType(4), state.DataTypePresence) // VPP presence/heartbeat data
require.Equal(t, state.DataType(5), state.DataTypeCustom)
}
// TestAncora_TreeAlgorithms tests that all tree algorithms are valid.
func TestAncora_TreeAlgorithms(t *testing.T) {
// Verify tree algorithm constants
require.Equal(t, state.TreeAlgorithm(0), state.TreeAlgorithmSHA256)
require.Equal(t, state.TreeAlgorithm(1), state.TreeAlgorithmKeccak256)
require.Equal(t, state.TreeAlgorithm(2), state.TreeAlgorithmPoseidon)
}
// TestAncora_ErasureStatuses tests that all erasure statuses are valid.
func TestAncora_ErasureStatuses(t *testing.T) {
// Verify erasure status constants
require.Equal(t, state.ErasureStatus(0), state.ErasurePending)
require.Equal(t, state.ErasureStatus(1), state.ErasureConfirmed)
require.Equal(t, state.ErasureStatus(2), state.ErasureDenied)
}

View File

@ -4,20 +4,19 @@ import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/tutus-one/tutus-chain/pkg/config"
"github.com/tutus-one/tutus-chain/pkg/core/native"
"github.com/tutus-one/tutus-chain/pkg/core/native/noderoles"
"github.com/tutus-one/tutus-chain/pkg/core/state"
"github.com/tutus-one/tutus-chain/pkg/crypto/keys"
"github.com/tutus-one/tutus-chain/pkg/io"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"github.com/tutus-one/tutus-chain/pkg/tutustest"
"github.com/tutus-one/tutus-chain/pkg/tutustest/chain"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"github.com/tutus-one/tutus-chain/pkg/vm/emit"
"github.com/tutus-one/tutus-chain/pkg/vm/opcode"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func newNativeClient(t *testing.T, name string) *tutustest.ContractInvoker {
@ -170,41 +169,3 @@ func checkNodeRoles(t *testing.T, designateInvoker *tutustest.ContractInvoker, o
designateInvoker.InvokeFail(t, "", "getDesignatedByRole", int64(r), int64(index))
}
}
// newGovernmentHelper creates a GovernmentHelper for testing government contracts.
// This provides convenient methods for registering citizens, managing VTS, and
// testing cross-contract government operations.
func newGovernmentHelper(t *testing.T) *tutustest.GovernmentHelper {
bc, acc := chain.NewSingleWithCustomConfig(t, nil)
e := tutustest.NewExecutor(t, bc, acc, acc)
return tutustest.NewGovernmentHelper(t, e)
}
// newGovernmentHelperWithConfig creates a GovernmentHelper with custom blockchain config.
func newGovernmentHelperWithConfig(t *testing.T, f func(cfg *config.Blockchain)) *tutustest.GovernmentHelper {
bc, acc := chain.NewSingleWithCustomConfig(t, f)
e := tutustest.NewExecutor(t, bc, acc, acc)
return tutustest.NewGovernmentHelper(t, e)
}
// newRoleHelper creates a RoleHelper for testing role-based operations.
func newRoleHelper(t *testing.T) *tutustest.RoleHelper {
bc, acc := chain.NewSingleWithCustomConfig(t, nil)
e := tutustest.NewExecutor(t, bc, acc, acc)
return tutustest.NewRoleHelper(t, e)
}
// newCrossContractHelper creates a CrossContractHelper for testing cross-contract calls.
func newCrossContractHelper(t *testing.T) *tutustest.CrossContractHelper {
bc, acc := chain.NewSingleWithCustomConfig(t, nil)
e := tutustest.NewExecutor(t, bc, acc, acc)
return tutustest.NewCrossContractHelper(t, e)
}
// newEventMatcher creates an EventMatcher from transaction events.
func newEventMatcher(t *testing.T, c *tutustest.ContractInvoker, txHash util.Uint256) *tutustest.EventMatcher {
aer, err := c.Chain.GetAppExecResults(txHash, 0)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
return tutustest.NewEventMatcher(t, aer[0].Events)
}

View File

@ -1,76 +0,0 @@
// Package vitahelper provides a helper contract for testing Vita-dependent operations.
// Many Tutus contracts use GetCallingScriptHash() for authorization, requiring
// cross-contract calls to properly test these paths.
package vitahelper
import (
"github.com/tutus-one/tutus-chain/pkg/interop"
"github.com/tutus-one/tutus-chain/pkg/interop/contract"
"github.com/tutus-one/tutus-chain/pkg/interop/native/management"
"github.com/tutus-one/tutus-chain/pkg/interop/runtime"
)
// VitaHash is the Vita native contract hash (from nativehashes).
// This must match the actual Vita contract hash.
const VitaHash = "\xd9\x0f\x0d\x15\x59\x8c\x45\xcc\xe5\xe0\x48\x69\x5b\x6b\x32\x78\x26\x5c\x58\x54"
// RegisterVita calls Vita.register() from this contract's context.
// This tests cross-contract registration where the caller is this helper contract.
func RegisterVita(vitaHash interop.Hash160, owner interop.Hash160, birthTimestamp int) bool {
return contract.Call(vitaHash, "register", contract.All, owner, birthTimestamp).(bool)
}
// GetVitaOwner calls Vita.ownerOf() for a token ID.
func GetVitaOwner(vitaHash interop.Hash160, tokenID []byte) interop.Hash160 {
return contract.Call(vitaHash, "ownerOf", contract.ReadStates, tokenID).(interop.Hash160)
}
// GetVitaBalance returns the Vita balance for an account.
func GetVitaBalance(vitaHash interop.Hash160, owner interop.Hash160) int {
return contract.Call(vitaHash, "balanceOf", contract.ReadStates, owner).(int)
}
// GetTotalVita returns the total number of Vita tokens.
func GetTotalVita(vitaHash interop.Hash160) int {
return contract.Call(vitaHash, "totalSupply", contract.ReadStates).(int)
}
// IsVitaHolder checks if an address has a Vita token.
func IsVitaHolder(vitaHash interop.Hash160, owner interop.Hash160) bool {
return contract.Call(vitaHash, "balanceOf", contract.ReadStates, owner).(int) > 0
}
// GetCallerHash returns the calling script hash for testing.
// Useful for verifying GetCallingScriptHash() behavior.
func GetCallerHash() interop.Hash160 {
return runtime.GetCallingScriptHash()
}
// GetExecutingHash returns this contract's hash.
func GetExecutingHash() interop.Hash160 {
return runtime.GetExecutingScriptHash()
}
// CallWithContext calls a contract method and returns whether this contract
// is correctly identified as the caller.
func CallWithContext(targetHash interop.Hash160, method string, args []any) any {
return contract.Call(targetHash, method, contract.All, args...)
}
// ProxyCall is a generic proxy for calling any contract method.
// Useful for testing authorization that depends on GetCallingScriptHash().
func ProxyCall(targetHash interop.Hash160, method string, args []any) any {
return contract.Call(targetHash, method, contract.All, args...)
}
// GetContractHash returns the hash of a contract by its name.
// Useful for getting native contract hashes at runtime.
func GetContractHash(name string) interop.Hash160 {
cs := management.GetContractByID(-1) // Management is always -1
if cs == nil {
return nil
}
// For getting native hashes by name, we'd need to call management.getContract
// This is a simplified version that just returns nil
return nil
}

View File

@ -1,6 +0,0 @@
name: VitaHelper
sourceurl: https://github.com/tutus-one/tutus-chain
supportedstandards: []
events: []
permissions:
- methods: '*'

View File

@ -143,11 +143,6 @@ func TestManagement_GenesisNativeState(t *testing.T) {
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
si := stack[0]
if _, ok := expected[name]; !ok {
// During active development, skip checking contracts not in expected map.
// New contracts added to nativenames.All won't be in CSS maps yet.
if skipStrictComparison {
return
}
require.Equal(t, stackitem.Null{}, si, fmt.Errorf("contract %s state found", name))
return
}

View File

@ -61,6 +61,4 @@ var (
Collocatio = util.Uint160{0xf9, 0x9c, 0x85, 0xeb, 0xea, 0x3, 0xa0, 0xd0, 0x69, 0x29, 0x13, 0x95, 0xdd, 0x33, 0xbc, 0x55, 0x53, 0xc6, 0x28, 0xf5}
// Annos is a hash of native Annos contract.
Annos = util.Uint160{0xaa, 0xad, 0x31, 0x3a, 0x1a, 0x53, 0x92, 0xd9, 0x98, 0x51, 0xee, 0xa7, 0xe3, 0x14, 0x36, 0xaa, 0x7e, 0xc8, 0xca, 0xf8}
// Ancora is a hash of native Ancora contract.
Ancora = util.Uint160{0x30, 0x5f, 0x26, 0x1b, 0x64, 0xdb, 0xfe, 0x5a, 0x2a, 0x37, 0x54, 0x52, 0xc6, 0x98, 0x5c, 0xd3, 0x3, 0x2d, 0xc1, 0x92}
)

View File

@ -59,6 +59,4 @@ const (
Collocatio int32 = -25
// Annos is an ID of native Annos contract (lifespan/years tracking).
Annos int32 = -26
// Ancora is an ID of native Ancora contract (Merkle root anchoring for off-chain data).
Ancora int32 = -27
)

View File

@ -26,9 +26,8 @@ const (
Opus = "Opus"
Palam = "Palam"
Pons = "Pons"
Collocatio = "Collocatio"
Annos = "Annos"
Ancora = "Ancora"
Collocatio = "Collocatio"
Annos = "Annos"
)
// All contains the list of all native contract names ordered by the contract ID.
@ -59,7 +58,6 @@ var All = []string{
Pons,
Collocatio,
Annos,
Ancora,
}
// IsValid checks if the name is a valid native contract's name.
@ -89,6 +87,5 @@ func IsValid(name string) bool {
name == Palam ||
name == Pons ||
name == Collocatio ||
name == Annos ||
name == Ancora
name == Annos
}

View File

@ -63,13 +63,8 @@ const (
RecoveryApprovalEvent = "RecoveryApproval"
RecoveryExecutedEvent = "RecoveryExecuted"
RecoveryCancelledEvent = "RecoveryCancelled"
VestingUpdatedEvent = "VestingUpdated"
)
// Default vesting period in blocks (approximately 30 days at 1 second per block).
// New Vita tokens must vest before having full rights (Sybil resistance).
const defaultVestingPeriod uint32 = 2592000 // 30 days * 24 hours * 60 minutes * 60 seconds
// Various errors.
var (
ErrTokenAlreadyExists = errors.New("token already exists for this owner")
@ -82,7 +77,6 @@ var (
ErrInvalidPersonHash = errors.New("invalid person hash")
ErrNotCommittee = errors.New("invalid committee signature")
ErrVitaInvalidWitness = errors.New("invalid witness")
ErrVitaNotFullyVested = errors.New("vita is not fully vested")
ErrAttributeNotFound = errors.New("attribute not found")
ErrAttributeRevoked = errors.New("attribute is already revoked")
ErrAttributeExpired = errors.New("attribute has expired")
@ -327,25 +321,6 @@ func newVita() *Vita {
md = NewMethodAndPrice(v.requirePermission, 1<<15, callflag.ReadStates)
v.AddMethod(md, desc)
// SetVesting method (committee only)
desc = NewDescriptor("setVesting", smartcontract.BoolType,
manifest.NewParameter("tokenId", smartcontract.IntegerType),
manifest.NewParameter("vestedUntil", smartcontract.IntegerType))
md = NewMethodAndPrice(v.setVesting, 1<<16, callflag.States|callflag.AllowNotify)
v.AddMethod(md, desc)
// IsFullyVested method
desc = NewDescriptor("isFullyVested", smartcontract.BoolType,
manifest.NewParameter("tokenId", smartcontract.IntegerType))
md = NewMethodAndPrice(v.isFullyVested, 1<<15, callflag.ReadStates)
v.AddMethod(md, desc)
// GetVestingInfo method
desc = NewDescriptor("getVestingInfo", smartcontract.ArrayType,
manifest.NewParameter("tokenId", smartcontract.IntegerType))
md = NewMethodAndPrice(v.getVestingInfo, 1<<15, callflag.ReadStates)
v.AddMethod(md, desc)
// Events
eDesc := NewEventDescriptor(VitaCreatedEvent,
manifest.NewParameter("tokenId", smartcontract.ByteArrayType),
@ -421,12 +396,6 @@ func newVita() *Vita {
manifest.NewParameter("cancelledBy", smartcontract.Hash160Type))
v.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(VestingUpdatedEvent,
manifest.NewParameter("tokenId", smartcontract.IntegerType),
manifest.NewParameter("vestedUntil", smartcontract.IntegerType),
manifest.NewParameter("updatedBy", smartcontract.Hash160Type))
v.AddEvent(NewEvent(eDesc))
return v
}
@ -665,10 +634,7 @@ func (v *Vita) register(ic *interop.Context, args []stackitem.Item) stackitem.It
// Get next token ID
tokenID := v.getAndIncrementTokenCounter(ic.DAO)
// Create token with vesting period for Sybil resistance
// New tokens must vest before having full rights
vestedUntil := ic.Block.Index + defaultVestingPeriod
// Create token
token := &state.Vita{
TokenID: tokenID,
Owner: owner,
@ -679,7 +645,6 @@ func (v *Vita) register(ic *interop.Context, args []stackitem.Item) stackitem.It
Status: state.TokenStatusActive,
StatusReason: "",
RecoveryHash: recoveryHash,
VestedUntil: vestedUntil,
}
// Store token
@ -957,22 +922,6 @@ func (v *Vita) GetTotalTokenCount(d *dao.Simple) uint64 {
return v.getTokenCounter(d)
}
// ExistsInternal checks if a Vita token with the given ID exists.
func (v *Vita) ExistsInternal(d *dao.Simple, vitaID uint64) bool {
token, err := v.getTokenByIDInternal(d, vitaID)
return err == nil && token != nil
}
// OwnerOfInternal returns the owner of a Vita token by ID.
// Returns empty Uint160 if token doesn't exist.
func (v *Vita) OwnerOfInternal(d *dao.Simple, vitaID uint64) util.Uint160 {
token, err := v.getTokenByIDInternal(d, vitaID)
if err != nil || token == nil {
return util.Uint160{}
}
return token.Owner
}
// IsAdultVerified checks if the owner has a verified "age_verified" attribute
// indicating they are 18+ years old. Used for age-restricted purchases.
// The attribute must be non-revoked and not expired.
@ -2139,124 +2088,3 @@ func (v *Vita) HasCoreRole(ic *interop.Context, tokenID uint64, role CoreRole) b
roles := v.getCoreRoles(ic, token)
return roles&(1<<uint64(role)) != 0
}
// Vesting methods for Sybil resistance
// setVesting sets or updates the vesting period for a Vita token (committee only).
// This can be used to accelerate vesting for verified identities or extend it for suspicious ones.
func (v *Vita) setVesting(ic *interop.Context, args []stackitem.Item) stackitem.Item {
tokenID := toBigInt(args[0]).Uint64()
vestedUntil := uint32(toBigInt(args[1]).Int64())
// Check committee
if !v.checkCommittee(ic) {
panic(ErrNotCommittee)
}
// Get token
token, err := v.getTokenByIDInternal(ic.DAO, tokenID)
if err != nil {
panic(err)
}
if token == nil {
panic(ErrTokenNotFound)
}
// Check token is not revoked
if token.Status == state.TokenStatusRevoked {
panic(ErrTokenRevoked)
}
// Update vesting
token.VestedUntil = vestedUntil
token.UpdatedAt = ic.Block.Index
// Store updated token
if err := v.putToken(ic.DAO, token); err != nil {
panic(err)
}
// Get caller for event
caller := ic.VM.GetCallingScriptHash()
// Emit event
err = ic.AddNotification(v.Hash, VestingUpdatedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(tokenID))),
stackitem.NewBigInteger(big.NewInt(int64(vestedUntil))),
stackitem.NewByteArray(caller.BytesBE()),
}))
if err != nil {
panic(err)
}
return stackitem.NewBool(true)
}
// isFullyVested checks if a Vita token has completed its vesting period.
// Returns true if the current block height is >= vestedUntil.
func (v *Vita) isFullyVested(ic *interop.Context, args []stackitem.Item) stackitem.Item {
tokenID := toBigInt(args[0]).Uint64()
token, err := v.getTokenByIDInternal(ic.DAO, tokenID)
if err != nil {
panic(err)
}
if token == nil {
panic(ErrTokenNotFound)
}
// Token is fully vested if current block >= vestedUntil
// VestedUntil of 0 means immediately vested (legacy tokens)
isVested := token.VestedUntil == 0 || ic.Block.Index >= token.VestedUntil
return stackitem.NewBool(isVested)
}
// getVestingInfo returns vesting information for a Vita token.
// Returns [vestedUntil, isFullyVested, remainingBlocks] or null if token not found.
func (v *Vita) getVestingInfo(ic *interop.Context, args []stackitem.Item) stackitem.Item {
tokenID := toBigInt(args[0]).Uint64()
token, err := v.getTokenByIDInternal(ic.DAO, tokenID)
if err != nil {
panic(err)
}
if token == nil {
return stackitem.Null{}
}
isVested := token.VestedUntil == 0 || ic.Block.Index >= token.VestedUntil
var remainingBlocks uint32
if !isVested && token.VestedUntil > ic.Block.Index {
remainingBlocks = token.VestedUntil - ic.Block.Index
}
return stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(token.VestedUntil))),
stackitem.NewBool(isVested),
stackitem.NewBigInteger(big.NewInt(int64(remainingBlocks))),
})
}
// Public methods for cross-native vesting access
// IsFullyVestedInternal checks if a Vita token has completed its vesting period.
// For cross-contract use by other native contracts like Collocatio and Eligere.
func (v *Vita) IsFullyVestedInternal(d *dao.Simple, tokenID uint64, currentBlock uint32) bool {
token, err := v.getTokenByIDInternal(d, tokenID)
if err != nil || token == nil {
return false
}
// VestedUntil of 0 means immediately vested (legacy tokens)
return token.VestedUntil == 0 || currentBlock >= token.VestedUntil
}
// GetVestedUntil returns the vesting block height for a Vita token.
// Returns 0 if token not found.
func (v *Vita) GetVestedUntil(d *dao.Simple, tokenID uint64) uint32 {
token, err := v.getTokenByIDInternal(d, tokenID)
if err != nil || token == nil {
return 0
}
return token.VestedUntil
}

View File

@ -1087,10 +1087,6 @@ type CollocatioConfig struct {
// Violation thresholds
MaxViolationsBeforeBan uint8 // Violations before permanent ban (default: 3)
ViolationCooldown uint32 // Blocks before violation expires (default: 1000000)
// Commit-reveal timing (anti-front-running)
CommitRevealDelay uint32 // Min blocks between commit and reveal (default: 10)
CommitRevealWindow uint32 // Max blocks to reveal after delay (default: 1000)
}
// DecodeBinary implements the io.Serializable interface.
@ -1109,8 +1105,6 @@ func (c *CollocatioConfig) DecodeBinary(br *io.BinReader) {
c.MinMaturityPeriod = br.ReadU32LE()
c.MaxViolationsBeforeBan = br.ReadB()
c.ViolationCooldown = br.ReadU32LE()
c.CommitRevealDelay = br.ReadU32LE()
c.CommitRevealWindow = br.ReadU32LE()
}
// EncodeBinary implements the io.Serializable interface.
@ -1129,8 +1123,6 @@ func (c *CollocatioConfig) EncodeBinary(bw *io.BinWriter) {
bw.WriteU32LE(c.MinMaturityPeriod)
bw.WriteB(c.MaxViolationsBeforeBan)
bw.WriteU32LE(c.ViolationCooldown)
bw.WriteU32LE(c.CommitRevealDelay)
bw.WriteU32LE(c.CommitRevealWindow)
}
// ToStackItem implements stackitem.Convertible interface.
@ -1150,8 +1142,6 @@ func (c *CollocatioConfig) ToStackItem() (stackitem.Item, error) {
stackitem.NewBigInteger(big.NewInt(int64(c.MinMaturityPeriod))),
stackitem.NewBigInteger(big.NewInt(int64(c.MaxViolationsBeforeBan))),
stackitem.NewBigInteger(big.NewInt(int64(c.ViolationCooldown))),
stackitem.NewBigInteger(big.NewInt(int64(c.CommitRevealDelay))),
stackitem.NewBigInteger(big.NewInt(int64(c.CommitRevealWindow))),
}), nil
}
@ -1161,8 +1151,8 @@ func (c *CollocatioConfig) FromStackItem(item stackitem.Item) error {
if !ok {
return errors.New("not a struct")
}
if len(items) != 16 {
return fmt.Errorf("wrong number of elements: expected 16, got %d", len(items))
if len(items) != 14 {
return fmt.Errorf("wrong number of elements: expected 14, got %d", len(items))
}
minPIO, err := items[0].TryInteger()
@ -1249,18 +1239,6 @@ func (c *CollocatioConfig) FromStackItem(item stackitem.Item) error {
}
c.ViolationCooldown = uint32(cooldown.Uint64())
commitDelay, err := items[14].TryInteger()
if err != nil {
return fmt.Errorf("invalid commitRevealDelay: %w", err)
}
c.CommitRevealDelay = uint32(commitDelay.Uint64())
commitWindow, err := items[15].TryInteger()
if err != nil {
return fmt.Errorf("invalid commitRevealWindow: %w", err)
}
c.CommitRevealWindow = uint32(commitWindow.Uint64())
return nil
}
@ -1281,156 +1259,5 @@ func DefaultCollocatioConfig() CollocatioConfig {
MinMaturityPeriod: 50000, // ~50000 blocks
MaxViolationsBeforeBan: 3,
ViolationCooldown: 1000000, // ~1M blocks
CommitRevealDelay: 10, // ~10 blocks minimum between commit and reveal
CommitRevealWindow: 1000, // ~1000 blocks to reveal after delay
}
}
// CommitmentStatus represents the status of an investment commitment.
type CommitmentStatus uint8
// Commitment statuses.
const (
CommitmentPending CommitmentStatus = 0 // Awaiting reveal
CommitmentRevealed CommitmentStatus = 1 // Successfully revealed and invested
CommitmentExpired CommitmentStatus = 2 // Expired without reveal
CommitmentCanceled CommitmentStatus = 3 // Canceled by investor
)
// InvestmentCommitment represents a commit-reveal commitment for investment.
// This prevents front-running attacks by hiding the investment amount until reveal.
type InvestmentCommitment struct {
ID uint64 // Unique commitment ID
OpportunityID uint64 // Opportunity being invested in
VitaID uint64 // Investor's Vita ID
Investor util.Uint160 // Investor's address
Commitment util.Uint256 // hash(amount || nonce || investor)
Status CommitmentStatus // Current status
CommittedAt uint32 // Block height when committed
RevealDeadline uint32 // Block height by which reveal must occur
RevealedAmount uint64 // Amount revealed (0 until revealed)
InvestmentID uint64 // Resulting investment ID (0 until revealed)
}
// DecodeBinary implements the io.Serializable interface.
func (c *InvestmentCommitment) DecodeBinary(br *io.BinReader) {
c.ID = br.ReadU64LE()
c.OpportunityID = br.ReadU64LE()
c.VitaID = br.ReadU64LE()
br.ReadBytes(c.Investor[:])
br.ReadBytes(c.Commitment[:])
c.Status = CommitmentStatus(br.ReadB())
c.CommittedAt = br.ReadU32LE()
c.RevealDeadline = br.ReadU32LE()
c.RevealedAmount = br.ReadU64LE()
c.InvestmentID = br.ReadU64LE()
}
// EncodeBinary implements the io.Serializable interface.
func (c *InvestmentCommitment) EncodeBinary(bw *io.BinWriter) {
bw.WriteU64LE(c.ID)
bw.WriteU64LE(c.OpportunityID)
bw.WriteU64LE(c.VitaID)
bw.WriteBytes(c.Investor[:])
bw.WriteBytes(c.Commitment[:])
bw.WriteB(byte(c.Status))
bw.WriteU32LE(c.CommittedAt)
bw.WriteU32LE(c.RevealDeadline)
bw.WriteU64LE(c.RevealedAmount)
bw.WriteU64LE(c.InvestmentID)
}
// ToStackItem implements stackitem.Convertible interface.
func (c *InvestmentCommitment) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(c.ID))),
stackitem.NewBigInteger(big.NewInt(int64(c.OpportunityID))),
stackitem.NewBigInteger(big.NewInt(int64(c.VitaID))),
stackitem.NewByteArray(c.Investor.BytesBE()),
stackitem.NewByteArray(c.Commitment.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(c.Status))),
stackitem.NewBigInteger(big.NewInt(int64(c.CommittedAt))),
stackitem.NewBigInteger(big.NewInt(int64(c.RevealDeadline))),
stackitem.NewBigInteger(big.NewInt(int64(c.RevealedAmount))),
stackitem.NewBigInteger(big.NewInt(int64(c.InvestmentID))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *InvestmentCommitment) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 10 {
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
}
id, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid id: %w", err)
}
c.ID = id.Uint64()
oppID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid opportunityID: %w", err)
}
c.OpportunityID = oppID.Uint64()
vitaID, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
c.VitaID = vitaID.Uint64()
investor, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid investor: %w", err)
}
c.Investor, err = util.Uint160DecodeBytesBE(investor)
if err != nil {
return fmt.Errorf("invalid investor address: %w", err)
}
commitment, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid commitment: %w", err)
}
c.Commitment, err = util.Uint256DecodeBytesBE(commitment)
if err != nil {
return fmt.Errorf("invalid commitment hash: %w", err)
}
status, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
c.Status = CommitmentStatus(status.Uint64())
committedAt, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid committedAt: %w", err)
}
c.CommittedAt = uint32(committedAt.Uint64())
revealDeadline, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid revealDeadline: %w", err)
}
c.RevealDeadline = uint32(revealDeadline.Uint64())
revealedAmount, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid revealedAmount: %w", err)
}
c.RevealedAmount = revealedAmount.Uint64()
investmentID, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid investmentID: %w", err)
}
c.InvestmentID = investmentID.Uint64()
return nil
}

View File

@ -1,423 +0,0 @@
package state
import (
"errors"
"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"
)
var errAncoraInvalidStackItem = errors.New("invalid stack item")
// DataType represents the type of off-chain data being anchored.
type DataType uint8
const (
// DataTypeMedical is for Salus healthcare records (HIPAA protected).
DataTypeMedical DataType = 0
// DataTypeEducation is for Scire education records (certifications, transcripts).
DataTypeEducation DataType = 1
// DataTypeInvestment is for Collocatio investment records (portfolios, transactions).
DataTypeInvestment DataType = 2
// DataTypeDocuments is for personal documents (IPFS CIDs).
DataTypeDocuments DataType = 3
// DataTypePresence is for VPP presence attestations (real-time humanity verification).
DataTypePresence DataType = 4
// DataTypeCustom is for government-defined extensions.
DataTypeCustom DataType = 5
)
// TreeAlgorithm represents the Merkle tree hash algorithm.
type TreeAlgorithm uint8
const (
// TreeAlgorithmSHA256 is the default SHA256 algorithm.
TreeAlgorithmSHA256 TreeAlgorithm = 0
// TreeAlgorithmKeccak256 is Keccak256 for EVM compatibility.
TreeAlgorithmKeccak256 TreeAlgorithm = 1
// TreeAlgorithmPoseidon is Poseidon for ZK-proof friendliness.
TreeAlgorithmPoseidon TreeAlgorithm = 2
)
// ErasureStatus represents the status of a GDPR erasure request.
type ErasureStatus uint8
const (
// ErasurePending means the request is awaiting off-chain deletion.
ErasurePending ErasureStatus = 0
// ErasureConfirmed means off-chain deletion has been confirmed.
ErasureConfirmed ErasureStatus = 1
// ErasureDenied means the erasure was denied (legal hold, etc.).
ErasureDenied ErasureStatus = 2
)
// RootInfo contains metadata about a Merkle root anchored on-chain.
type RootInfo struct {
// Root is the 32-byte Merkle root.
Root []byte
// LeafCount is the number of leaves in the tree.
LeafCount uint64
// UpdatedAt is the block height of the last update.
UpdatedAt uint32
// UpdatedBy is the provider script hash that updated this root.
UpdatedBy util.Uint160
// Version is the incrementing version number.
Version uint64
// TreeAlgorithm is the hash algorithm used (0=SHA256, 1=Keccak256, 2=Poseidon).
TreeAlgorithm TreeAlgorithm
// SchemaVersion is the data schema version (e.g., "1.0.0").
SchemaVersion string
// ContentHash is an optional hash of the serialized tree.
ContentHash []byte
}
// EncodeBinary implements io.Serializable.
func (r *RootInfo) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(r.Root)
w.WriteU64LE(r.LeafCount)
w.WriteU32LE(r.UpdatedAt)
r.UpdatedBy.EncodeBinary(w)
w.WriteU64LE(r.Version)
w.WriteB(byte(r.TreeAlgorithm))
w.WriteString(r.SchemaVersion)
w.WriteVarBytes(r.ContentHash)
}
// DecodeBinary implements io.Serializable.
func (r *RootInfo) DecodeBinary(br *io.BinReader) {
r.Root = br.ReadVarBytes()
r.LeafCount = br.ReadU64LE()
r.UpdatedAt = br.ReadU32LE()
r.UpdatedBy.DecodeBinary(br)
r.Version = br.ReadU64LE()
r.TreeAlgorithm = TreeAlgorithm(br.ReadB())
r.SchemaVersion = br.ReadString()
r.ContentHash = br.ReadVarBytes()
}
// ToStackItem converts RootInfo to a stack item for VM.
func (r *RootInfo) ToStackItem() (stackitem.Item, error) {
return stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(r.Root),
stackitem.NewBigInteger(big.NewInt(int64(r.LeafCount))),
stackitem.NewBigInteger(big.NewInt(int64(r.UpdatedAt))),
stackitem.NewByteArray(r.UpdatedBy.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(r.Version))),
stackitem.NewBigInteger(big.NewInt(int64(r.TreeAlgorithm))),
stackitem.NewByteArray([]byte(r.SchemaVersion)),
stackitem.NewByteArray(r.ContentHash),
}), nil
}
// FromStackItem populates RootInfo from a stack item.
func (r *RootInfo) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok || len(arr) < 8 {
return errAncoraInvalidStackItem
}
root, err := arr[0].TryBytes()
if err != nil {
return err
}
r.Root = root
leafCount, err := arr[1].TryInteger()
if err != nil {
return err
}
r.LeafCount = leafCount.Uint64()
updatedAt, err := arr[2].TryInteger()
if err != nil {
return err
}
r.UpdatedAt = uint32(updatedAt.Uint64())
updatedBy, err := arr[3].TryBytes()
if err != nil {
return err
}
r.UpdatedBy, err = util.Uint160DecodeBytesBE(updatedBy)
if err != nil {
return err
}
version, err := arr[4].TryInteger()
if err != nil {
return err
}
r.Version = version.Uint64()
algo, err := arr[5].TryInteger()
if err != nil {
return err
}
r.TreeAlgorithm = TreeAlgorithm(algo.Uint64())
schema, err := arr[6].TryBytes()
if err != nil {
return err
}
r.SchemaVersion = string(schema)
contentHash, err := arr[7].TryBytes()
if err != nil {
return err
}
r.ContentHash = contentHash
return nil
}
// ErasureInfo contains metadata about a GDPR erasure request.
type ErasureInfo struct {
// RequestedAt is the block height when erasure was requested.
RequestedAt uint32
// RequestedBy is the script hash of the requester.
RequestedBy util.Uint160
// Reason is the GDPR reason code.
Reason string
// Status is the current erasure status.
Status ErasureStatus
// ProcessedAt is when off-chain deletion was confirmed.
ProcessedAt uint32
// ConfirmedBy is the provider that confirmed deletion.
ConfirmedBy util.Uint160
// DeniedReason is the reason for denial (if denied).
DeniedReason string
}
// EncodeBinary implements io.Serializable.
func (e *ErasureInfo) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(e.RequestedAt)
e.RequestedBy.EncodeBinary(w)
w.WriteString(e.Reason)
w.WriteB(byte(e.Status))
w.WriteU32LE(e.ProcessedAt)
e.ConfirmedBy.EncodeBinary(w)
w.WriteString(e.DeniedReason)
}
// DecodeBinary implements io.Serializable.
func (e *ErasureInfo) DecodeBinary(br *io.BinReader) {
e.RequestedAt = br.ReadU32LE()
e.RequestedBy.DecodeBinary(br)
e.Reason = br.ReadString()
e.Status = ErasureStatus(br.ReadB())
e.ProcessedAt = br.ReadU32LE()
e.ConfirmedBy.DecodeBinary(br)
e.DeniedReason = br.ReadString()
}
// ToStackItem converts ErasureInfo to a stack item for VM.
func (e *ErasureInfo) ToStackItem() (stackitem.Item, error) {
return stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(e.RequestedAt))),
stackitem.NewByteArray(e.RequestedBy.BytesBE()),
stackitem.NewByteArray([]byte(e.Reason)),
stackitem.NewBigInteger(big.NewInt(int64(e.Status))),
stackitem.NewBigInteger(big.NewInt(int64(e.ProcessedAt))),
stackitem.NewByteArray(e.ConfirmedBy.BytesBE()),
stackitem.NewByteArray([]byte(e.DeniedReason)),
}), nil
}
// FromStackItem populates ErasureInfo from a stack item.
func (e *ErasureInfo) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok || len(arr) < 7 {
return errAncoraInvalidStackItem
}
requestedAt, err := arr[0].TryInteger()
if err != nil {
return err
}
e.RequestedAt = uint32(requestedAt.Uint64())
requestedBy, err := arr[1].TryBytes()
if err != nil {
return err
}
e.RequestedBy, err = util.Uint160DecodeBytesBE(requestedBy)
if err != nil {
return err
}
reason, err := arr[2].TryBytes()
if err != nil {
return err
}
e.Reason = string(reason)
status, err := arr[3].TryInteger()
if err != nil {
return err
}
e.Status = ErasureStatus(status.Uint64())
processedAt, err := arr[4].TryInteger()
if err != nil {
return err
}
e.ProcessedAt = uint32(processedAt.Uint64())
confirmedBy, err := arr[5].TryBytes()
if err != nil {
return err
}
e.ConfirmedBy, err = util.Uint160DecodeBytesBE(confirmedBy)
if err != nil {
return err
}
deniedReason, err := arr[6].TryBytes()
if err != nil {
return err
}
e.DeniedReason = string(deniedReason)
return nil
}
// ProviderConfig contains configuration for an authorized data provider.
type ProviderConfig struct {
// DataType is the data type this provider is authorized for.
DataType DataType
// Provider is the script hash of the authorized provider.
Provider util.Uint160
// Description is a human-readable description.
Description string
// RegisteredAt is the block height when registered.
RegisteredAt uint32
// Active indicates if the provider is currently active.
Active bool
// MaxUpdatesPerBlock is the anti-spam rate limit.
MaxUpdatesPerBlock uint32
// UpdateCooldown is the blocks between updates per VitaID.
UpdateCooldown uint32
}
// EncodeBinary implements io.Serializable.
func (p *ProviderConfig) EncodeBinary(w *io.BinWriter) {
w.WriteB(byte(p.DataType))
p.Provider.EncodeBinary(w)
w.WriteString(p.Description)
w.WriteU32LE(p.RegisteredAt)
w.WriteBool(p.Active)
w.WriteU32LE(p.MaxUpdatesPerBlock)
w.WriteU32LE(p.UpdateCooldown)
}
// DecodeBinary implements io.Serializable.
func (p *ProviderConfig) DecodeBinary(br *io.BinReader) {
p.DataType = DataType(br.ReadB())
p.Provider.DecodeBinary(br)
p.Description = br.ReadString()
p.RegisteredAt = br.ReadU32LE()
p.Active = br.ReadBool()
p.MaxUpdatesPerBlock = br.ReadU32LE()
p.UpdateCooldown = br.ReadU32LE()
}
// ToStackItem converts ProviderConfig to a stack item for VM.
func (p *ProviderConfig) ToStackItem() (stackitem.Item, error) {
return stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(p.DataType))),
stackitem.NewByteArray(p.Provider.BytesBE()),
stackitem.NewByteArray([]byte(p.Description)),
stackitem.NewBigInteger(big.NewInt(int64(p.RegisteredAt))),
stackitem.NewBool(p.Active),
stackitem.NewBigInteger(big.NewInt(int64(p.MaxUpdatesPerBlock))),
stackitem.NewBigInteger(big.NewInt(int64(p.UpdateCooldown))),
}), nil
}
// FromStackItem populates ProviderConfig from a stack item.
func (p *ProviderConfig) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok || len(arr) < 7 {
return errAncoraInvalidStackItem
}
dataType, err := arr[0].TryInteger()
if err != nil {
return err
}
p.DataType = DataType(dataType.Uint64())
provider, err := arr[1].TryBytes()
if err != nil {
return err
}
p.Provider, err = util.Uint160DecodeBytesBE(provider)
if err != nil {
return err
}
description, err := arr[2].TryBytes()
if err != nil {
return err
}
p.Description = string(description)
registeredAt, err := arr[3].TryInteger()
if err != nil {
return err
}
p.RegisteredAt = uint32(registeredAt.Uint64())
active, err := arr[4].TryBool()
if err != nil {
return err
}
p.Active = active
maxUpdates, err := arr[5].TryInteger()
if err != nil {
return err
}
p.MaxUpdatesPerBlock = uint32(maxUpdates.Uint64())
cooldown, err := arr[6].TryInteger()
if err != nil {
return err
}
p.UpdateCooldown = uint32(cooldown.Uint64())
return nil
}
// StateAnchorsConfig contains configuration for the StateAnchors contract.
type StateAnchorsConfig struct {
// DefaultTreeAlgorithm is the default hash algorithm (0=SHA256).
DefaultTreeAlgorithm TreeAlgorithm
// MaxProofDepth is the maximum depth of Merkle proofs (default: 32).
MaxProofDepth uint32
// DefaultMaxUpdatesPerBlock is the default rate limit.
DefaultMaxUpdatesPerBlock uint32
// DefaultUpdateCooldown is the default cooldown in blocks.
DefaultUpdateCooldown uint32
// MaxHistoryVersions is the max history to retain per vitaID+dataType.
MaxHistoryVersions uint32
// ErasureGracePeriod is blocks before erasure can be denied.
ErasureGracePeriod uint32
// AttestationValidBlocks is how long attestations are valid.
AttestationValidBlocks uint32
}
// DefaultStateAnchorsConfig returns the default configuration.
func DefaultStateAnchorsConfig() StateAnchorsConfig {
return StateAnchorsConfig{
DefaultTreeAlgorithm: TreeAlgorithmSHA256,
MaxProofDepth: 32,
DefaultMaxUpdatesPerBlock: 10,
DefaultUpdateCooldown: 1,
MaxHistoryVersions: 100,
ErasureGracePeriod: 1000, // ~16 minutes at 1s blocks
AttestationValidBlocks: 86400, // ~24 hours
}
}

View File

@ -65,7 +65,6 @@ type Vita struct {
Status TokenStatus // Current status
StatusReason string // Reason for status change
RecoveryHash []byte // Hash of recovery mechanism
VestedUntil uint32 // Block height until which the Vita is vesting (Sybil resistance)
}
// ToStackItem implements stackitem.Convertible interface.
@ -80,7 +79,6 @@ func (t *Vita) ToStackItem() (stackitem.Item, error) {
stackitem.NewBigInteger(big.NewInt(int64(t.Status))),
stackitem.NewByteArray([]byte(t.StatusReason)),
stackitem.NewByteArray(t.RecoveryHash),
stackitem.NewBigInteger(big.NewInt(int64(t.VestedUntil))),
}), nil
}
@ -90,8 +88,8 @@ func (t *Vita) FromStackItem(item stackitem.Item) error {
if !ok {
return errors.New("not a struct")
}
if len(items) != 10 {
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
if len(items) != 9 {
return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
}
tokenID, err := items[0].TryInteger()
@ -149,12 +147,6 @@ func (t *Vita) FromStackItem(item stackitem.Item) error {
return fmt.Errorf("invalid recoveryHash: %w", err)
}
vestedUntil, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid vestedUntil: %w", err)
}
t.VestedUntil = uint32(vestedUntil.Int64())
return nil
}

View File

@ -419,7 +419,7 @@ func TestClientNEOContract(t *testing.T) {
sym, err := neoR.Symbol()
require.NoError(t, err)
require.Equal(t, "TUT", sym)
require.Equal(t, "NEO", sym)
dec, err := neoR.Decimals()
require.NoError(t, err)
@ -452,7 +452,7 @@ func TestClientNEOContract(t *testing.T) {
acc0 := testchain.PrivateKey(0).PublicKey().GetScriptHash()
uncl, err := neoR.UnclaimedGas(acc0, chain.BlockHeight()+1)
require.NoError(t, err)
require.Equal(t, big.NewInt(10500), uncl)
require.Equal(t, big.NewInt(10000), uncl)
accState, err := neoR.GetAccountState(acc0)
require.NoError(t, err)
@ -1309,7 +1309,7 @@ func TestClient_NEP11_ND(t *testing.T) {
t.Run("TotalSupply", func(t *testing.T) {
s, err := n11.TotalSupply()
require.NoError(t, err)
require.EqualValues(t, big.NewInt(1), s) // the only `tutus.one` of acc0
require.EqualValues(t, big.NewInt(1), s) // the only `tutus.com` of acc0
})
t.Run("Symbol", func(t *testing.T) {
sym, err := n11.Symbol()
@ -1333,7 +1333,7 @@ func TestClient_NEP11_ND(t *testing.T) {
require.EqualValues(t, big.NewInt(1), b)
})
t.Run("OwnerOf", func(t *testing.T) {
b, err := n11.OwnerOf([]byte("tutus.one"))
b, err := n11.OwnerOf([]byte("tutus.com"))
require.NoError(t, err)
require.EqualValues(t, acc, b)
})
@ -1343,28 +1343,28 @@ func TestClient_NEP11_ND(t *testing.T) {
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
require.NoError(t, err)
require.Equal(t, 1, len(items))
require.Equal(t, [][]byte{[]byte("tutus.one")}, items)
require.Equal(t, [][]byte{[]byte("tutus.com")}, items)
require.NoError(t, iter.Terminate())
})
t.Run("TokensExpanded", func(t *testing.T) {
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
require.NoError(t, err)
require.Equal(t, [][]byte{[]byte("tutus.one")}, items)
require.Equal(t, [][]byte{[]byte("tutus.com")}, items)
})
t.Run("Properties", func(t *testing.T) {
p, err := n11.Properties([]byte("tutus.one"))
p, err := n11.Properties([]byte("tutus.com"))
require.NoError(t, err)
blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(15)) // `tutus.one` domain was registered in 14th block
blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(14)) // `tutus.com` domain was registered in 14th block
require.NoError(t, err)
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
expected := stackitem.NewMap()
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("tutus.one")))
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("tutus.com")))
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp+365*24*3600*1000)) // expiration formula
expected.Add(stackitem.Make([]byte("admin")), stackitem.Null{})
require.EqualValues(t, expected, p)
})
t.Run("Transfer", func(t *testing.T) {
_, _, err := n11.Transfer(testchain.PrivateKeyByID(1).GetScriptHash(), []byte("tutus.one"), nil)
_, _, err := n11.Transfer(testchain.PrivateKeyByID(1).GetScriptHash(), []byte("tutus.com"), nil)
require.NoError(t, err)
})
}
@ -1462,7 +1462,7 @@ func TestClient_NNS(t *testing.T) {
nnc := nns.NewReader(invoker.New(c, nil), nnsHash)
t.Run("IsAvailable, false", func(t *testing.T) {
b, err := nnc.IsAvailable("tutus.one")
b, err := nnc.IsAvailable("tutus.com")
require.NoError(t, err)
require.Equal(t, false, b)
})
@ -1472,7 +1472,7 @@ func TestClient_NNS(t *testing.T) {
require.Equal(t, true, b)
})
t.Run("Resolve, good", func(t *testing.T) {
b, err := nnc.Resolve("tutus.one", nns.A)
b, err := nnc.Resolve("tutus.com", nns.A)
require.NoError(t, err)
require.Equal(t, "1.2.3.4", b)
})
@ -1485,23 +1485,23 @@ func TestClient_NNS(t *testing.T) {
require.Error(t, err)
})
t.Run("GetAllRecords, good", func(t *testing.T) {
iter, err := nnc.GetAllRecords("tutus.one")
iter, err := nnc.GetAllRecords("tutus.com")
require.NoError(t, err)
arr, err := iter.Next(config.DefaultMaxIteratorResultItems)
require.NoError(t, err)
require.Equal(t, 1, len(arr))
require.Equal(t, nns.RecordState{
Name: "tutus.one",
Name: "tutus.com",
Type: nns.A,
Data: "1.2.3.4",
}, arr[0])
})
t.Run("GetAllRecordsExpanded, good", func(t *testing.T) {
rss, err := nnc.GetAllRecordsExpanded("tutus.one", 42)
rss, err := nnc.GetAllRecordsExpanded("tutus.com", 42)
require.NoError(t, err)
require.Equal(t, []nns.RecordState{
{
Name: "tutus.one",
Name: "tutus.com",
Type: nns.A,
Data: "1.2.3.4",
},
@ -1880,7 +1880,7 @@ func TestClient_Wait(t *testing.T) {
b1, err := chain.GetBlock(chain.GetHeaderHash(1))
require.NoError(t, err)
require.True(t, len(b1.Transactions) > 0)
b23, err := chain.GetBlock(chain.GetHeaderHash(24)) // block with faulted tx and extended VUB.
b23, err := chain.GetBlock(chain.GetHeaderHash(23)) // block with faulted tx and extended VUB.
require.NoError(t, err)
require.True(t, len(b23.Transactions) > 0)
@ -2452,7 +2452,7 @@ func TestClient_FindStorageHistoric(t *testing.T) {
t.Cleanup(c.Close)
require.NoError(t, c.Init())
root, err := util.Uint256DecodeStringLE(block21StateRootLE)
root, err := util.Uint256DecodeStringLE(block20StateRootLE)
require.NoError(t, err)
h, err := util.Uint160DecodeStringLE(testContractHashLE)
require.NoError(t, err)
@ -2521,7 +2521,7 @@ func TestClient_GetStorageHistoric(t *testing.T) {
t.Cleanup(c.Close)
require.NoError(t, c.Init())
root, err := util.Uint256DecodeStringLE(block21StateRootLE)
root, err := util.Uint256DecodeStringLE(block20StateRootLE)
require.NoError(t, err)
h, err := util.Uint160DecodeStringLE(testContractHashLE)
require.NoError(t, err)

View File

@ -83,46 +83,46 @@ const (
genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
// testContractHashLE is an LE hash of NEP-17 "Rubl" contract deployed at block #2
// of basic testing chain.
testContractHashLE = "4ae8b123905cf5f17c56c7ba03c3e49dd4de6602"
testContractHashLE = "8cf5e69031cee431d780dd9e4b024bf5bb6e8eba"
// deploymentTxHash is an LE hash of transaction that deploys NEP-17 "Rubl"
// contract at block #2 of basic testing chain.
deploymentTxHash = "372dbbbb097ec67171c979f218668854cc91dcb4b73f6f00f69a9b709b67b16b"
deploymentTxHash = "48cd6fcacde126cef88900fd2ca254eafc25cae8152cb343ef7c18352e141356"
// verifyContractHash is an LE hash of "Verify" contract deployed at block #7 of
// basic testing chain.
verifyContractHash = "41ea4991de3c83011ad040c5e0837f4f47d088a4"
verifyContractHash = "7f732d9aa2e877a36142344822826721522f2524"
// verifyContractAVM is a base64-encoded AVM of "Verify" contract deployed at block #7 of
// basic testing chain.
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
// verifyWithArgsContractHash is an LE hash of "VerifyWithArgs" contract deployed
// at block #10 of basic testing chain.
verifyWithArgsContractHash = "a7b1353ce63445c132b3951185cfda5d3fad3bc9"
verifyWithArgsContractHash = "e7c553997feb14dc60b226a662b72f9a98bcdd41"
// nnsContractHash is an LE hash of NEP-11 non-divisible "examples/nft-nd-nns"
// contract deployed at block #11 of basic testing chain.
nnsContractHash = "9e78165b94215c954e3db4897f8fad0eec57eee5"
nnsContractHash = "c17da07ba65ba62a07134c171cf8c50d02f17995"
// nnsToken1ID is a hex-encoded ID of the first NEP-11 NNS token minted at block
// #15 of basic testing chain.
nnsToken1ID = "74757475732e6f6e65"
// #14 of basic testing chain.
nnsToken1ID = "6e656f2e636f6d"
// nfsoContractHash is an LE hash of NEP-11 divisible "examples/nft-d" ("NeoFS
// Object") contract deployed at block #18 of basic testing chain.
nfsoContractHash = "01ad70f110f3a42196927b90bd29cda89285e047"
// Object") contract deployed at block #17 of basic testing chain.
nfsoContractHash = "5635edb13674bb6f09d18b09fce6b69aee206931"
// nfsoToken1ID is a hex-encoded ID of the first NEP-11 NFSO token minted at
// block #19 of basic testing chain.
// block #18 of basic testing chain.
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
// storageContractHash is an LE hash of "Storage" contract deployed at block #23
// storageContractHash is an LE hash of "Storage" contract deployed at block #22
// of basic testing chain.
storageContractHash = "4fc98af155564b1540d990346ec793f429827ad8"
// faultedTxHashLE is an LE hash of FAULTed transaction accepted at block #24 of
storageContractHash = "c3b3a914d94326a0aff9197c4d6db418dd4b1b35"
// faultedTxHashLE is an LE hash of FAULTed transaction accepted at block #23 of
// basic testing chain.
faultedTxHashLE = "82f9a502799c13b3ced6fdb83f0eec0fee5814b7c59868beca2e39b38c6527d3"
faultedTxHashLE = "c8d69b83f085eb2608da555016c0debe4e8e6a6e8a78bfa30d3491b910d1a33b"
// faultedTxBlock is the number of block of basic testing chain that contains
// FAULTed transaction.
faultedTxBlock uint32 = 24
faultedTxBlock uint32 = 23
// invokescriptContractAVM is a base64-encoded AVM of
// "pkg/internal/basicchain/testdata/invokescript_contract.go" contract that is
// not yet deployed to the testing basic chain.
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
// block21StateRootLE is an LE stateroot of block #21 of basic testing chain.
block21StateRootLE = "81404d5b7bcd88eae9990ce7d80ba963f28e765faa3039d2dcf87dc9a67ea212"
// block20StateRootLE is an LE stateroot of block #20 of basic testing chain.
block20StateRootLE = "16c5b5909250dead6695d3a3349909b9ca813ef109598017146a8e0c64658cad"
)
var (
@ -176,7 +176,7 @@ var rpcFunctionsWithUnsupportedStatesTestCases = map[string][]rpcTestCase{
"findstates": {
{
name: "unsupported state",
params: `["` + block21StateRootLE + `", "0xabcdef"]`,
params: `["` + block20StateRootLE + `", "0xabcdef"]`,
fail: true,
errCode: tutusrpc.ErrUnsupportedStateCode,
},
@ -184,7 +184,7 @@ var rpcFunctionsWithUnsupportedStatesTestCases = map[string][]rpcTestCase{
"findstoragehistoric": {
{
name: "unsupported state",
params: `["` + block21StateRootLE + `", "0xabcdef"]`,
params: `["` + block20StateRootLE + `", "0xabcdef"]`,
fail: true,
errCode: tutusrpc.ErrUnsupportedStateCode,
},
@ -406,11 +406,11 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive",
params: `["` + nnsContractHash + `", "` + nnsToken1ID + `"]`,
params: `["` + nnsContractHash + `", "6e656f2e636f6d"]`,
result: func(e *executor) any {
return &map[string]any{
"name": "tutus.one",
"expiration": "lxbLRl0B",
"name": "neo.com",
"expiration": "lhbLRl0B",
"admin": nil,
}
},
@ -621,25 +621,25 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "invalid contract",
params: `["` + block21StateRootLE + `", "0xabcdef"]`,
params: `["` + block20StateRootLE + `", "0xabcdef"]`,
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid prefix",
params: `["` + block21StateRootLE + `", "` + testContractHashLE + `", "notabase64%"]`,
params: `["` + block20StateRootLE + `", "` + testContractHashLE + `", "notabase64%"]`,
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid key",
params: `["` + block21StateRootLE + `", "` + testContractHashLE + `", "QQ==", "notabase64%"]`,
params: `["` + block20StateRootLE + `", "` + testContractHashLE + `", "QQ==", "notabase64%"]`,
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "unknown contract/large count",
params: `["` + block21StateRootLE + `", "0000000000000000000000000000000000000000", "QQ==", "QQ==", 101]`,
params: `["` + block20StateRootLE + `", "0000000000000000000000000000000000000000", "QQ==", "QQ==", 101]`,
fail: true,
errCode: tutusrpc.ErrUnknownContractCode,
},
@ -715,7 +715,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"getstoragehistoric": {
{
name: "positive",
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block21StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa10"))),
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa10"))),
result: func(e *executor) any {
v := base64.StdEncoding.EncodeToString([]byte("v2"))
return &v
@ -723,7 +723,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "missing key",
params: fmt.Sprintf(`["%s", "%s", "dGU="]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s", "dGU="]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.ErrUnknownStorageItemCode,
},
@ -735,13 +735,13 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "no second parameter",
params: fmt.Sprintf(`["%s"]`, block21StateRootLE),
params: fmt.Sprintf(`["%s"]`, block20StateRootLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "no third parameter",
params: fmt.Sprintf(`["%s", "%s"]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s"]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
@ -753,13 +753,13 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "invalid hash",
params: fmt.Sprintf(`["%s", "notahex"]`, block21StateRootLE),
params: fmt.Sprintf(`["%s", "notahex"]`, block20StateRootLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid key",
params: fmt.Sprintf(`["%s", "%s", "notabase64$"]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s", "notabase64$"]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
@ -907,7 +907,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"findstoragehistoric": {
{
name: "not truncated",
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block21StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa1"))),
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa1"))),
result: func(_ *executor) any { return new(result.FindStorage) },
check: func(t *testing.T, e *executor, res any) {
actual, ok := res.(*result.FindStorage)
@ -928,7 +928,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "truncated first page",
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block21StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa"))),
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa"))),
result: func(_ *executor) any { return new(result.FindStorage) },
check: func(t *testing.T, e *executor, res any) {
actual, ok := res.(*result.FindStorage)
@ -953,7 +953,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "truncated second page",
params: fmt.Sprintf(`["%s","%s", "%s", 2]`, block21StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa"))),
params: fmt.Sprintf(`["%s","%s", "%s", 2]`, block20StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa"))),
result: func(_ *executor) any { return new(result.FindStorage) },
check: func(t *testing.T, e *executor, res any) {
actual, ok := res.(*result.FindStorage)
@ -974,7 +974,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "empty prefix",
params: fmt.Sprintf(`["%s", "%s", ""]`, block21StateRootLE, nnsContractHash),
params: fmt.Sprintf(`["%s", "%s", ""]`, block20StateRootLE, nnsContractHash),
result: func(_ *executor) any { return new(result.FindStorage) },
check: func(t *testing.T, e *executor, res any) {
actual, ok := res.(*result.FindStorage)
@ -999,7 +999,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "unknown key",
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block21StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("unknown-key"))),
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHashLE, base64.StdEncoding.EncodeToString([]byte("unknown-key"))),
result: func(_ *executor) any { return new(result.FindStorage) },
check: func(t *testing.T, e *executor, res any) {
actual, ok := res.(*result.FindStorage)
@ -1027,31 +1027,31 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "no second parameter",
params: fmt.Sprintf(`["%s"]`, block21StateRootLE),
params: fmt.Sprintf(`["%s"]`, block20StateRootLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "no third parameter",
params: fmt.Sprintf(`["%s", "%s"]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s"]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid hash",
params: fmt.Sprintf(`["%s", "notahex"]`, block21StateRootLE),
params: fmt.Sprintf(`["%s", "notahex"]`, block20StateRootLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid key",
params: fmt.Sprintf(`["%s", "%s", "notabase64$"]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s", "notabase64$"]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
{
name: "invalid page",
params: fmt.Sprintf(`["%s", "%s", "", "not-an-int"]`, block21StateRootLE, testContractHashLE),
params: fmt.Sprintf(`["%s", "%s", "", "not-an-int"]`, block20StateRootLE, testContractHashLE),
fail: true,
errCode: tutusrpc.InvalidParamsCode,
},
@ -1342,7 +1342,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
expected := result.UnclaimedGas{
Address: testchain.MultisigScriptHash(),
Unclaimed: *big.NewInt(12000),
Unclaimed: *big.NewInt(11500),
}
assert.Equal(t, expected, *actual)
},
@ -1428,13 +1428,13 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, with notifications",
params: `["` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"tutus.one"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
params: `["` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
result: func(e *executor) any {
script := append([]byte{0x0b, 0x0c, 0x09, 0x74, 0x75, 0x74, 0x75, 0x73, 0x2e, 0x6f, 0x6e, 0x65, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14}, nnsHash.BytesBE()...)
script := append([]byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14}, nnsHash.BytesBE()...)
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
return &result.Invoke{
State: "HALT",
GasConsumed: 32122730,
GasConsumed: 31922730,
Script: script,
Stack: []stackitem.Item{stackitem.Make(true)},
Notifications: []state.NotificationEvent{{
@ -1444,7 +1444,7 @@ var rpcTestCases = map[string][]rpcTestCase{
stackitem.Make([]byte{0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x08, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}),
stackitem.Make([]byte{0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b}),
stackitem.Make(1),
stackitem.Make("tutus.one"),
stackitem.Make("neo.com"),
}),
}},
}
@ -1484,15 +1484,15 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, verbose",
params: `["` + nnsContractHash + `", "resolve", [{"type":"String", "value":"tutus.one"},{"type":"Integer","value":1}], [], true]`,
params: `["` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`,
result: func(e *executor) any {
script := append([]byte{0x11, 0xc, 0x9, 0x74, 0x75, 0x74, 0x75, 0x73, 0x2e, 0x6f, 0x6e, 0x65, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14}, nnsHash.BytesBE()...)
script := append([]byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14}, nnsHash.BytesBE()...)
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib)
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
return &result.Invoke{
State: "HALT",
GasConsumed: 14507250,
GasConsumed: 14460630,
Script: script,
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
@ -1567,7 +1567,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, by stateroot",
params: `["` + block21StateRootLE + `", "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
params: `["` + block20StateRootLE + `", "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
result: func(e *executor) any { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv any) {
res, ok := inv.(*result.Invoke)
@ -1579,13 +1579,13 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, with notifications",
params: `[20, "` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"tutus.one"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
params: `[20, "` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
result: func(e *executor) any {
script := append([]byte{0x0b, 0x0c, 0x09, 0x74, 0x75, 0x74, 0x75, 0x73, 0x2e, 0x6f, 0x6e, 0x65, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14}, nnsHash.BytesBE()...)
script := append([]byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14}, nnsHash.BytesBE()...)
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
return &result.Invoke{
State: "HALT",
GasConsumed: 32122730,
GasConsumed: 31922730,
Script: script,
Stack: []stackitem.Item{stackitem.Make(true)},
Notifications: []state.NotificationEvent{{
@ -1595,7 +1595,7 @@ var rpcTestCases = map[string][]rpcTestCase{
stackitem.Make([]byte{0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x08, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}),
stackitem.Make([]byte{0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b}),
stackitem.Make(1),
stackitem.Make("tutus.one"),
stackitem.Make("neo.com"),
}),
}},
}
@ -1603,15 +1603,15 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, verbose",
params: `[20, "` + nnsContractHash + `", "resolve", [{"type":"String", "value":"tutus.one"},{"type":"Integer","value":1}], [], true]`,
params: `[20, "` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`,
result: func(e *executor) any {
script := append([]byte{0x11, 0xc, 0x9, 0x74, 0x75, 0x74, 0x75, 0x73, 0x2e, 0x6f, 0x6e, 0x65, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14}, nnsHash.BytesBE()...)
script := append([]byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14}, nnsHash.BytesBE()...)
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib)
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
return &result.Invoke{
State: "HALT",
GasConsumed: 14507250,
GasConsumed: 14460630,
Script: script,
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
@ -1806,7 +1806,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, by stateroot",
params: `["` + block21StateRootLE + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
params: `["` + block20StateRootLE + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
result: func(e *executor) any { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv any) {
res, ok := inv.(*result.Invoke)
@ -2079,7 +2079,7 @@ var rpcTestCases = map[string][]rpcTestCase{
},
{
name: "positive, by stateroot",
params: fmt.Sprintf(`["`+block21StateRootLE+`","%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
params: fmt.Sprintf(`["`+block20StateRootLE+`","%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
result: func(e *executor) any { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv any) {
res, ok := inv.(*result.Invoke)
@ -2357,7 +2357,6 @@ var rpcTestCases = map[string][]rpcTestCase{
}
func TestRPC(t *testing.T) {
t.Skip("Skipped: requires testblocks.acc regeneration after .one TLD addition - assigned to tutustest framework task")
t.Run("http", func(t *testing.T) {
testRPCProtocol(t, doRPCCallOverHTTP)
})
@ -2792,14 +2791,14 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
}
t.Run("ByHeight", func(t *testing.T) { testRoot(t, strconv.FormatInt(5, 10)) })
t.Run("ByHash", func(t *testing.T) { testRoot(t, `"`+chain.GetHeaderHash(5).StringLE()+`"`) })
t.Run("21", func(t *testing.T) {
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [21]}`
t.Run("20", func(t *testing.T) {
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [20]}`
body := doRPCCall(rpc, httpSrv.URL, t)
rawRes := checkErrGetResult(t, body, false, 0)
res := &state.MPTRoot{}
require.NoError(t, json.Unmarshal(rawRes, res))
require.Equal(t, block21StateRootLE, res.Root.StringLE())
require.Equal(t, block20StateRootLE, res.Root.StringLE())
})
})
t.Run("getstate", func(t *testing.T) {
@ -2828,9 +2827,9 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
checkErrGetResult(t, body, true, tutusrpc.InvalidParamsCode)
})
t.Run("good: fresh state", func(t *testing.T) {
root, err := e.chain.GetStateModule().GetStateRoot(17)
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
// `testkey`-`newtestvalue` pair was put to the contract storage at block #17
// `testkey`-`newtestvalue` pair was put to the contract storage at block #16
params := fmt.Sprintf(`"%s", "%s", "%s"`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("testkey")))
testGetState(t, params, base64.StdEncoding.EncodeToString([]byte("newtestvalue")))
})
@ -2863,8 +2862,8 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.Equal(t, expected.Truncated, actual.Truncated)
}
t.Run("good: no prefix, no limit", func(t *testing.T) {
// pairs for this test where put to the contract storage at block #17
root, err := e.chain.GetStateModule().GetStateRoot(17)
// pairs for this test where put to the contract storage at block #16
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
params := fmt.Sprintf(`"%s", "%s", "%s"`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa")))
testFindStates(t, params, root.Root, result.FindStates{
@ -2878,7 +2877,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
})
t.Run("good: empty prefix, no limit", func(t *testing.T) {
// empty prefix should be considered as no prefix specified.
root, err := e.chain.GetStateModule().GetStateRoot(17)
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
params := fmt.Sprintf(`"%s", "%s", "%s", ""`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa")))
testFindStates(t, params, root.Root, result.FindStates{
@ -2892,7 +2891,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
})
t.Run("good: empty prefix, no limit, no data", func(t *testing.T) {
// empty prefix should be considered as no prefix specified.
root, err := e.chain.GetStateModule().GetStateRoot(21)
root, err := e.chain.GetStateModule().GetStateRoot(20)
require.NoError(t, err)
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib) // It has no data.
params := fmt.Sprintf(`"%s", "%s", ""`, root.Root.StringLE(), stdHash.StringLE())
@ -2902,8 +2901,8 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
})
})
t.Run("good: with prefix, no limit", func(t *testing.T) {
// pairs for this test where put to the contract storage at block #17
root, err := e.chain.GetStateModule().GetStateRoot(17)
// pairs for this test where put to the contract storage at block #16
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
params := fmt.Sprintf(`"%s", "%s", "%s", "%s"`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa")), base64.StdEncoding.EncodeToString([]byte("aa10")))
testFindStates(t, params, root.Root, result.FindStates{
@ -2915,8 +2914,8 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
})
t.Run("good: empty prefix, with limit", func(t *testing.T) {
for limit := 2; limit < 5; limit++ {
// pairs for this test where put to the contract storage at block #17
root, err := e.chain.GetStateModule().GetStateRoot(17)
// pairs for this test where put to the contract storage at block #16
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
params := fmt.Sprintf(`"%s", "%s", "%s", "", %d`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa")), limit)
expected := result.FindStates{
@ -2933,8 +2932,8 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
}
})
t.Run("good: with prefix, with limit", func(t *testing.T) {
// pairs for this test where put to the contract storage at block #17
root, err := e.chain.GetStateModule().GetStateRoot(17)
// pairs for this test where put to the contract storage at block #16
root, err := e.chain.GetStateModule().GetStateRoot(16)
require.NoError(t, err)
params := fmt.Sprintf(`"%s", "%s", "%s", "%s", %d`, root.Root.StringLE(), testContractHashLE, base64.StdEncoding.EncodeToString([]byte("aa")), base64.StdEncoding.EncodeToString([]byte("aa00")), 1)
testFindStates(t, params, root.Root, result.FindStates{
@ -3101,12 +3100,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoError(t, json.Unmarshal(res, actual))
checkNep17TransfersAux(t, e, actual, sent, rcvd)
}
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{21, 22, 23, 24}, []int{4, 5}) })
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{19, 20, 21, 22}, []int{3, 4}) })
t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) })
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{18, 19}, []int{3}) })
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{21}, []int{4}) })
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{19, 20}, []int{4}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{21, 22}, []int{5}) })
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{16, 17}, []int{2}) })
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{19}, []int{3}) })
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{18, 19}, []int{3}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{20, 21}, []int{4}) })
})
prepareIteratorSession := func(t *testing.T) (uuid.UUID, uuid.UUID) {
@ -3803,7 +3802,7 @@ func checkNep11Balances(t *testing.T, e *executor, acc any) {
{
ID: nnsToken1ID,
Amount: "1",
LastUpdated: 15,
LastUpdated: 14,
},
},
},
@ -3816,7 +3815,7 @@ func checkNep11Balances(t *testing.T, e *executor, acc any) {
{
ID: nfsoToken1ID,
Amount: "80",
LastUpdated: 22,
LastUpdated: 21,
},
},
},
@ -3847,15 +3846,15 @@ func checkNep17Balances(t *testing.T, e *executor, acc any) {
Amount: "99998000",
LastUpdated: 4,
Name: "Tutus",
Symbol: "TUT",
Symbol: "NEO",
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "90663858090",
LastUpdated: 24,
Amount: "90614597330",
LastUpdated: 23,
Decimals: 8,
Name: "Lub",
Symbol: "LUB",
Symbol: "GAS",
}},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
}
@ -3871,22 +3870,22 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
res, ok := acc.(*result.NEP11Transfers)
require.True(t, ok)
blockReceiveNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(22)) // transfer 0.05 NFSO from priv1 back to priv0.
blockReceiveNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(21)) // transfer 0.05 NFSO from priv1 back to priv0.
require.NoError(t, err)
require.Equal(t, 1, len(blockReceiveNFSO.Transactions))
txReceiveNFSO := blockReceiveNFSO.Transactions[0]
blockSendNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(20)) // transfer 0.25 NFSO from priv0 to priv1.
blockSendNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(19)) // transfer 0.25 NFSO from priv0 to priv1.
require.NoError(t, err)
require.Equal(t, 1, len(blockSendNFSO.Transactions))
txSendNFSO := blockSendNFSO.Transactions[0]
blockMintNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(19)) // mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
blockMintNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(18)) // mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
require.NoError(t, err)
require.Equal(t, 1, len(blockMintNFSO.Transactions))
txMintNFSO := blockMintNFSO.Transactions[0]
blockRegisterNSRecordA, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // register `tutus.one` with A record type and priv0 owner via NS
blockRegisterNSRecordA, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // register `neo.com` with A record type and priv0 owner via NS
require.NoError(t, err)
require.Equal(t, 1, len(blockRegisterNSRecordA.Transactions))
txRegisterNSRecordA := blockRegisterNSRecordA.Transactions[0]
@ -3904,7 +3903,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Address: testchain.PrivateKeyByID(1).Address(), // to priv1
ID: nfsoToken1ID, // NFSO ID
Amount: big.NewInt(25).String(),
Index: 20,
Index: 19,
TxHash: txSendNFSO.Hash(),
},
},
@ -3915,7 +3914,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
ID: nfsoToken1ID,
Address: testchain.PrivateKeyByID(1).Address(), // from priv1
Amount: "5",
Index: 22,
Index: 21,
TxHash: txReceiveNFSO.Hash(),
},
{
@ -3924,7 +3923,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
ID: nfsoToken1ID,
Address: "", // minting
Amount: "100",
Index: 19,
Index: 18,
TxHash: txMintNFSO.Hash(),
},
{
@ -3933,7 +3932,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
ID: nnsToken1ID,
Address: "", // minting
Amount: "1",
Index: 15,
Index: 14,
TxHash: txRegisterNSRecordA.Hash(),
},
},
@ -3960,7 +3959,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
}
func checkNep17Transfers(t *testing.T, e *executor, acc any) {
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
}
func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int) {
@ -3974,27 +3973,27 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
require.Equal(t, 1, len(blockWithFAULTedTx.Transactions))
txFAULTed := blockWithFAULTedTx.Transactions[0]
blockDeploy6, err := e.chain.GetBlock(e.chain.GetHeaderHash(23)) // deploy Storage contract (storage_contract.go)
blockDeploy6, err := e.chain.GetBlock(e.chain.GetHeaderHash(22)) // deploy Storage contract (storage_contract.go)
require.NoError(t, err)
require.Equal(t, 1, len(blockDeploy6.Transactions))
txDeploy6 := blockDeploy6.Transactions[0]
blockTransferNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(20)) // transfer 0.25 NFSO from priv0 to priv1.
blockTransferNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(19)) // transfer 0.25 NFSO from priv0 to priv1.
require.NoError(t, err)
require.Equal(t, 1, len(blockTransferNFSO.Transactions))
txTransferNFSO := blockTransferNFSO.Transactions[0]
blockMintNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(19)) // mint 1.00 NFSO token for priv0 by transferring 10 GAS to NFSO contract.
blockMintNFSO, err := e.chain.GetBlock(e.chain.GetHeaderHash(18)) // mint 1.00 NFSO token for priv0 by transferring 10 GAS to NFSO contract.
require.NoError(t, err)
require.Equal(t, 1, len(blockMintNFSO.Transactions))
txMintNFSO := blockMintNFSO.Transactions[0]
blockDeploy5, err := e.chain.GetBlock(e.chain.GetHeaderHash(18)) // deploy NeoFS Object contract (NEP11-Divisible)
blockDeploy5, err := e.chain.GetBlock(e.chain.GetHeaderHash(17)) // deploy NeoFS Object contract (NEP11-Divisible)
require.NoError(t, err)
require.Equal(t, 1, len(blockDeploy5.Transactions))
txDeploy5 := blockDeploy5.Transactions[0]
blockPutNewTestValue, err := e.chain.GetBlock(e.chain.GetHeaderHash(17)) // invoke `put` method of `test_contract.go` with `testkey`, `newtestvalue` args
blockPutNewTestValue, err := e.chain.GetBlock(e.chain.GetHeaderHash(16)) // invoke `put` method of `test_contract.go` with `testkey`, `newtestvalue` args
require.NoError(t, err)
require.Equal(t, 4, len(blockPutNewTestValue.Transactions))
txPutNewTestValue := blockPutNewTestValue.Transactions[0]
@ -4002,28 +4001,19 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
txPutValue2 := blockPutNewTestValue.Transactions[2] // invoke `put` method of `test_contract.go` with `aa10`, `v2` args
txPutValue3 := blockPutNewTestValue.Transactions[3] // invoke `put` method of `test_contract.go` with `aa50`, `v3` args
blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(16)) // add type A record to `tutus.one` domain via NNS
blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // add type A record to `neo.com` domain via NNS
require.NoError(t, err)
require.Equal(t, 1, len(blockSetRecord.Transactions))
txSetRecord := blockSetRecord.Transactions[0]
blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // register `tutus.one` domain via NNS
blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // register `neo.com` domain via NNS
require.NoError(t, err)
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
txRegisterDomain := blockRegisterDomain.Transactions[0]
blockAddRoot, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // add `.one` root to NNS
require.NoError(t, err)
require.Equal(t, 1, len(blockAddRoot.Transactions))
txAddRoot := blockAddRoot.Transactions[0]
blockGASBounty2, err := e.chain.GetBlock(e.chain.GetHeaderHash(12)) // size of committee = 6
require.NoError(t, err)
blockGASBounty3 := blockDeploy5 // block 18 is multiple of committee size 6
blockGASBounty4 := blockWithFAULTedTx // block 24 is multiple of committee size 6
blockDeploy4, err := e.chain.GetBlock(e.chain.GetHeaderHash(11)) // deploy ns.go (non-native neo name service contract)
require.NoError(t, err)
require.Equal(t, 1, len(blockDeploy4.Transactions))
@ -4092,7 +4082,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txFAULTed.SystemFee + txFAULTed.NetworkFee).String(),
Index: 24,
Index: 23,
TxHash: blockWithFAULTedTx.Hash(),
},
{
@ -4100,7 +4090,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txDeploy6.SystemFee + txDeploy6.NetworkFee).String(),
Index: 23,
Index: 22,
TxHash: blockDeploy6.Hash(),
},
{
@ -4108,7 +4098,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txTransferNFSO.SystemFee + txTransferNFSO.NetworkFee).String(),
Index: 20,
Index: 19,
TxHash: blockTransferNFSO.Hash(),
},
{
@ -4116,7 +4106,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: address.Uint160ToString(nfsoHash),
Amount: "1000000000",
Index: 19,
Index: 18,
NotifyIndex: 0,
TxHash: txMintNFSO.Hash(),
},
@ -4125,7 +4115,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txMintNFSO.SystemFee + txMintNFSO.NetworkFee).String(),
Index: 19,
Index: 18,
TxHash: blockMintNFSO.Hash(),
},
{
@ -4133,7 +4123,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txDeploy5.SystemFee + txDeploy5.NetworkFee).String(),
Index: 18,
Index: 17,
TxHash: blockDeploy5.Hash(),
},
{
@ -4141,7 +4131,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue3.SystemFee + txPutValue3.NetworkFee).String(),
Index: 17,
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
@ -4149,7 +4139,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue2.SystemFee + txPutValue2.NetworkFee).String(),
Index: 17,
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
@ -4157,7 +4147,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue1.SystemFee + txPutValue1.NetworkFee).String(),
Index: 17,
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
@ -4165,7 +4155,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutNewTestValue.SystemFee + txPutNewTestValue.NetworkFee).String(),
Index: 17,
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
@ -4173,7 +4163,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txSetRecord.SystemFee + txSetRecord.NetworkFee).String(),
Index: 16,
Index: 15,
TxHash: blockSetRecord.Hash(),
},
{
@ -4181,16 +4171,8 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txRegisterDomain.SystemFee + txRegisterDomain.NetworkFee).String(),
Index: 15,
TxHash: blockRegisterDomain.Hash(),
},
{
Timestamp: blockAddRoot.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txAddRoot.SystemFee + txAddRoot.NetworkFee).String(),
Index: 14,
TxHash: blockAddRoot.Hash(),
TxHash: blockRegisterDomain.Hash(),
},
{
Timestamp: blockDeploy4.Timestamp,
@ -4302,22 +4284,13 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc any, sent, rcvd []int
},
Received: []result.NEP17Transfer{
{
Timestamp: blockGASBounty4.Timestamp, // GAS bounty block 24
Asset: e.chain.UtilityTokenHash(),
Address: "",
Amount: "50000000",
Index: 24,
NotifyIndex: 0,
TxHash: blockGASBounty4.Hash(),
},
{
Timestamp: blockGASBounty3.Timestamp, // GAS bounty block 18
Timestamp: blockMintNFSO.Timestamp, // GAS bounty
Asset: e.chain.UtilityTokenHash(),
Address: "",
Amount: "50000000",
Index: 18,
NotifyIndex: 0,
TxHash: blockGASBounty3.Hash(),
TxHash: blockMintNFSO.Hash(),
},
{
Timestamp: blockGASBounty2.Timestamp,

Binary file not shown.

View File

@ -1,254 +0,0 @@
package tutustest
import (
"testing"
"github.com/tutus-one/tutus-chain/pkg/core/transaction"
"github.com/tutus-one/tutus-chain/pkg/io"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/emit"
"github.com/tutus-one/tutus-chain/pkg/vm/opcode"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// CrossContractHelper provides utilities for testing cross-contract calls.
// Many Tutus contracts use GetCallingScriptHash() for authorization, which
// requires deploying a helper contract to properly test these paths.
type CrossContractHelper struct {
*Executor
t testing.TB
}
// NewCrossContractHelper creates a helper for cross-contract testing.
func NewCrossContractHelper(t testing.TB, e *Executor) *CrossContractHelper {
return &CrossContractHelper{
Executor: e,
t: t,
}
}
// CallFromContract builds a script that calls a target contract method from
// another contract's context. This is useful for testing GetCallingScriptHash().
// The script will:
// 1. Call the target contract with the specified method and args
// 2. Return the result
func (c *CrossContractHelper) CallFromContract(
caller util.Uint160,
target util.Uint160,
method string,
args ...any,
) []byte {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, target, method, callflag.All, args...)
require.NoError(c.t, w.Err)
return w.Bytes()
}
// BuildProxyScript creates a script that acts as a proxy contract.
// It calls the target method and returns the result.
// Useful for testing authorization checks that require specific callers.
func (c *CrossContractHelper) BuildProxyScript(
target util.Uint160,
method string,
args ...any,
) []byte {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, target, method, callflag.All, args...)
require.NoError(c.t, w.Err)
return w.Bytes()
}
// BuildMultiCallScript creates a script that calls multiple contracts in sequence.
// Each call's result is collected and returned as an array.
func (c *CrossContractHelper) BuildMultiCallScript(calls []ContractCall) []byte {
w := io.NewBufBinWriter()
// Call each contract and collect results
for _, call := range calls {
emit.AppCall(w.BinWriter, call.Hash, call.Method, callflag.All, call.Args...)
}
// Pack results into array
emit.Int(w.BinWriter, int64(len(calls)))
emit.Opcodes(w.BinWriter, opcode.PACK)
require.NoError(c.t, w.Err)
return w.Bytes()
}
// ContractCall represents a single contract invocation.
type ContractCall struct {
Hash util.Uint160
Method string
Args []any
}
// BuildConditionalScript creates a script that calls one method if condition
// is true, otherwise calls another method.
func (c *CrossContractHelper) BuildConditionalScript(
condition bool,
target util.Uint160,
trueMethod string,
trueArgs []any,
falseMethod string,
falseArgs []any,
) []byte {
w := io.NewBufBinWriter()
if condition {
emit.AppCall(w.BinWriter, target, trueMethod, callflag.All, trueArgs...)
} else {
emit.AppCall(w.BinWriter, target, falseMethod, callflag.All, falseArgs...)
}
require.NoError(c.t, w.Err)
return w.Bytes()
}
// PrepareProxyCall creates a transaction that calls a contract method
// through a custom script, allowing the test to control the calling context.
func (c *CrossContractHelper) PrepareProxyCall(
signers []Signer,
script []byte,
) *transaction.Transaction {
tx := transaction.New(script, 0)
tx.Nonce = Nonce()
tx.ValidUntilBlock = c.Chain.BlockHeight() + 1
// Add signers
tx.Signers = make([]transaction.Signer, len(signers))
for i, s := range signers {
tx.Signers[i] = transaction.Signer{
Account: s.ScriptHash(),
Scopes: transaction.Global,
}
}
// Calculate fees and sign
AddNetworkFee(c.t, c.Chain, tx, signers...)
require.NoError(c.t, c.Chain.PoolTx(tx))
return tx
}
// InvokeViaProxy invokes a contract method through a proxy script
// and returns the result stack.
func (c *CrossContractHelper) InvokeViaProxy(
signers []Signer,
target util.Uint160,
method string,
args ...any,
) []stackitem.Item {
script := c.BuildProxyScript(target, method, args...)
tx := c.PrepareProxyCall(signers, script)
c.AddNewBlock(c.t, tx)
aer, err := c.Chain.GetAppExecResults(tx.Hash(), 0)
require.NoError(c.t, err)
require.Equal(c.t, 1, len(aer))
return aer[0].Stack
}
// TestContractBuilder helps build simple test contracts for cross-contract testing.
type TestContractBuilder struct {
t testing.TB
script []byte
}
// NewTestContractBuilder creates a builder for test contracts.
func NewTestContractBuilder(t testing.TB) *TestContractBuilder {
return &TestContractBuilder{t: t}
}
// WithCall adds a contract call to the test contract.
func (b *TestContractBuilder) WithCall(target util.Uint160, method string, args ...any) *TestContractBuilder {
w := io.NewBufBinWriter()
if len(b.script) > 0 {
w.BinWriter.WriteBytes(b.script)
}
emit.AppCall(w.BinWriter, target, method, callflag.All, args...)
require.NoError(b.t, w.Err)
b.script = w.Bytes()
return b
}
// WithAssertion adds an assertion that the top of the stack is true.
func (b *TestContractBuilder) WithAssertion() *TestContractBuilder {
w := io.NewBufBinWriter()
if len(b.script) > 0 {
w.BinWriter.WriteBytes(b.script)
}
emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(b.t, w.Err)
b.script = w.Bytes()
return b
}
// WithDrop drops the top stack item.
func (b *TestContractBuilder) WithDrop() *TestContractBuilder {
w := io.NewBufBinWriter()
if len(b.script) > 0 {
w.BinWriter.WriteBytes(b.script)
}
emit.Opcodes(w.BinWriter, opcode.DROP)
require.NoError(b.t, w.Err)
b.script = w.Bytes()
return b
}
// Build returns the final script.
func (b *TestContractBuilder) Build() []byte {
return b.script
}
// AuthorizationTestHelper provides utilities for testing authorization patterns.
type AuthorizationTestHelper struct {
*CrossContractHelper
}
// NewAuthorizationTestHelper creates a helper for testing authorization.
func NewAuthorizationTestHelper(t testing.TB, e *Executor) *AuthorizationTestHelper {
return &AuthorizationTestHelper{
CrossContractHelper: NewCrossContractHelper(t, e),
}
}
// TestCallerAuthorization tests that a method properly checks GetCallingScriptHash().
// It calls the method from different contexts and verifies the expected behavior.
func (a *AuthorizationTestHelper) TestCallerAuthorization(
target util.Uint160,
method string,
args []any,
authorizedCallers []util.Uint160,
unauthorizedCallers []util.Uint160,
signers []Signer,
) {
// Test authorized callers succeed
for _, caller := range authorizedCallers {
script := a.CallFromContract(caller, target, method, args...)
tx := a.PrepareProxyCall(signers, script)
a.AddNewBlock(a.t, tx)
aer, err := a.Chain.GetAppExecResults(tx.Hash(), 0)
require.NoError(a.t, err)
require.Equal(a.t, 1, len(aer))
require.Equal(a.t, "HALT", aer[0].VMState.String(),
"authorized caller %s should succeed", caller.StringLE())
}
// Test unauthorized callers fail
for _, caller := range unauthorizedCallers {
script := a.CallFromContract(caller, target, method, args...)
tx := a.PrepareProxyCall(signers, script)
a.AddNewBlock(a.t, tx)
aer, err := a.Chain.GetAppExecResults(tx.Hash(), 0)
require.NoError(a.t, err)
require.Equal(a.t, 1, len(aer))
require.Equal(a.t, "FAULT", aer[0].VMState.String(),
"unauthorized caller %s should fail", caller.StringLE())
}
}

View File

@ -1,295 +0,0 @@
package tutustest
import (
"testing"
"github.com/tutus-one/tutus-chain/pkg/core/state"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// EventMatcher provides fluent event validation for contract tests.
type EventMatcher struct {
t testing.TB
events []state.NotificationEvent
contract util.Uint160
}
// NewEventMatcher creates a matcher for the given events.
func NewEventMatcher(t testing.TB, events []state.NotificationEvent) *EventMatcher {
return &EventMatcher{
t: t,
events: events,
}
}
// FromContract filters events to only those from the specified contract.
func (m *EventMatcher) FromContract(hash util.Uint160) *EventMatcher {
m.contract = hash
return m
}
// HasEvent checks that at least one event with the given name exists.
func (m *EventMatcher) HasEvent(name string) *EventMatcher {
found := false
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
found = true
break
}
}
require.True(m.t, found, "expected event %q not found", name)
return m
}
// HasNoEvent checks that no event with the given name exists.
func (m *EventMatcher) HasNoEvent(name string) *EventMatcher {
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
require.NotEqual(m.t, name, e.Name, "unexpected event %q found", name)
}
return m
}
// CountEvents returns the number of events with the given name.
func (m *EventMatcher) CountEvents(name string) int {
count := 0
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
count++
}
}
return count
}
// RequireEventCount asserts exactly N events with the given name.
func (m *EventMatcher) RequireEventCount(name string, count int) *EventMatcher {
actual := m.CountEvents(name)
require.Equal(m.t, count, actual, "expected %d %q events, got %d", count, name, actual)
return m
}
// GetEvent returns the first event with the given name.
func (m *EventMatcher) GetEvent(name string) *state.NotificationEvent {
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
return &e
}
}
return nil
}
// GetEvents returns all events with the given name.
func (m *EventMatcher) GetEvents(name string) []state.NotificationEvent {
var result []state.NotificationEvent
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
result = append(result, e)
}
}
return result
}
// EventValidator provides detailed validation of a single event.
type EventValidator struct {
t testing.TB
event *state.NotificationEvent
}
// ValidateEvent creates a validator for a specific event.
func (m *EventMatcher) ValidateEvent(name string) *EventValidator {
event := m.GetEvent(name)
require.NotNil(m.t, event, "event %q not found", name)
return &EventValidator{
t: m.t,
event: event,
}
}
// HasArgs checks that the event has the expected number of arguments.
func (v *EventValidator) HasArgs(count int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Equal(v.t, count, len(arr), "expected %d args, got %d", count, len(arr))
return v
}
// ArgEquals checks that argument at index equals the expected value.
func (v *EventValidator) ArgEquals(index int, expected any) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
actual := arr[index]
exp := stackitem.Make(expected)
require.True(v.t, actual.Equals(exp), "arg[%d]: expected %v, got %v", index, expected, actual)
return v
}
// ArgIsHash160 checks that argument at index is a valid Hash160.
func (v *EventValidator) ArgIsHash160(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
bs, err := arr[index].TryBytes()
require.NoError(v.t, err, "arg[%d] is not bytes", index)
require.Equal(v.t, 20, len(bs), "arg[%d] is not Hash160 (len=%d)", index, len(bs))
return v
}
// ArgIsPositive checks that argument at index is a positive integer.
func (v *EventValidator) ArgIsPositive(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
n, err := arr[index].TryInteger()
require.NoError(v.t, err, "arg[%d] is not integer", index)
require.Greater(v.t, n.Int64(), int64(0), "arg[%d] is not positive", index)
return v
}
// ArgIsNonNegative checks that argument at index is a non-negative integer.
func (v *EventValidator) ArgIsNonNegative(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
n, err := arr[index].TryInteger()
require.NoError(v.t, err, "arg[%d] is not integer", index)
require.GreaterOrEqual(v.t, n.Int64(), int64(0), "arg[%d] is negative", index)
return v
}
// GetArg returns the argument at the given index.
func (v *EventValidator) GetArg(index int) stackitem.Item {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
return arr[index]
}
// GetArgBytes returns the argument at the given index as bytes.
func (v *EventValidator) GetArgBytes(index int) []byte {
item := v.GetArg(index)
bs, err := item.TryBytes()
require.NoError(v.t, err)
return bs
}
// GetArgInt returns the argument at the given index as int64.
func (v *EventValidator) GetArgInt(index int) int64 {
item := v.GetArg(index)
n, err := item.TryInteger()
require.NoError(v.t, err)
return n.Int64()
}
// GetArgHash160 returns the argument at the given index as Hash160.
func (v *EventValidator) GetArgHash160(index int) util.Uint160 {
bs := v.GetArgBytes(index)
require.Equal(v.t, 20, len(bs))
return util.Uint160(bs)
}
// Common Tutus event names for convenience
const (
// Vita events
EventVitaRegistered = "VitaRegistered"
EventVitaSuspended = "VitaSuspended"
EventVitaRevoked = "VitaRevoked"
// VTS events
EventTransfer = "Transfer"
EventMint = "Mint"
EventBurn = "Burn"
// Eligere events
EventProposalCreated = "ProposalCreated"
EventVoteCast = "VoteCast"
EventProposalPassed = "ProposalPassed"
EventProposalFailed = "ProposalFailed"
// Lex events
EventLawEnacted = "LawEnacted"
EventLawRepealed = "LawRepealed"
EventRightRestricted = "RightRestricted"
EventRightRestored = "RightRestored"
// RoleRegistry events
EventRoleGranted = "RoleGranted"
EventRoleRevoked = "RoleRevoked"
// Scire events
EventEnrollment = "Enrollment"
EventCertification = "Certification"
// Salus events
EventMedicalRecord = "MedicalRecord"
EventEmergencyAccess = "EmergencyAccess"
// Federation events
EventVisitorRegistered = "VisitorRegistered"
EventAsylumGranted = "AsylumGranted"
EventCitizenNaturalized = "CitizenNaturalized"
)
// TransferEventValidator is a specialized validator for Transfer events.
type TransferEventValidator struct {
*EventValidator
}
// ValidateTransfer creates a validator specifically for Transfer events.
func (m *EventMatcher) ValidateTransfer() *TransferEventValidator {
return &TransferEventValidator{
EventValidator: m.ValidateEvent(EventTransfer),
}
}
// From checks the sender of the transfer.
func (v *TransferEventValidator) From(expected util.Uint160) *TransferEventValidator {
v.ArgEquals(0, expected.BytesBE())
return v
}
// To checks the recipient of the transfer.
func (v *TransferEventValidator) To(expected util.Uint160) *TransferEventValidator {
v.ArgEquals(1, expected.BytesBE())
return v
}
// Amount checks the transfer amount.
func (v *TransferEventValidator) Amount(expected int64) *TransferEventValidator {
v.ArgEquals(2, expected)
return v
}
// IsMint checks that this is a mint (from is null).
func (v *TransferEventValidator) IsMint() *TransferEventValidator {
arr := v.event.Item.Value().([]stackitem.Item)
require.Equal(v.t, stackitem.AnyT, arr[0].Type(), "expected null sender for mint")
return v
}
// IsBurn checks that this is a burn (to is null).
func (v *TransferEventValidator) IsBurn() *TransferEventValidator {
arr := v.event.Item.Value().([]stackitem.Item)
require.Equal(v.t, stackitem.AnyT, arr[1].Type(), "expected null recipient for burn")
return v
}

View File

@ -1,237 +0,0 @@
package tutustest
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
"github.com/tutus-one/tutus-chain/pkg/core/transaction"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// GovernmentHelper provides utilities for testing Tutus government contracts.
// It wraps common operations like registering citizens, granting roles, and
// setting up cross-contract test scenarios.
type GovernmentHelper struct {
*Executor
t testing.TB
// Contract invokers for common government contracts
Vita *ContractInvoker
Lex *ContractInvoker
Eligere *ContractInvoker
Scire *ContractInvoker
Salus *ContractInvoker
VTS *ContractInvoker
Annos *ContractInvoker
Tribute *ContractInvoker
RoleReg *ContractInvoker
Treasury *ContractInvoker
}
// NewGovernmentHelper creates a helper for testing government contracts.
// It initializes invokers for all major government contracts.
func NewGovernmentHelper(t testing.TB, e *Executor) *GovernmentHelper {
g := &GovernmentHelper{
Executor: e,
t: t,
}
// Initialize contract invokers with committee authority
g.Vita = e.CommitteeInvoker(e.NativeHash(t, nativenames.Vita))
g.Lex = e.CommitteeInvoker(e.NativeHash(t, nativenames.Lex))
g.Eligere = e.CommitteeInvoker(e.NativeHash(t, nativenames.Eligere))
g.Scire = e.CommitteeInvoker(e.NativeHash(t, nativenames.Scire))
g.Salus = e.CommitteeInvoker(e.NativeHash(t, nativenames.Salus))
g.VTS = e.CommitteeInvoker(e.NativeHash(t, nativenames.VTS))
g.Annos = e.CommitteeInvoker(e.NativeHash(t, nativenames.Annos))
g.Tribute = e.CommitteeInvoker(e.NativeHash(t, nativenames.Tribute))
g.RoleReg = e.CommitteeInvoker(e.NativeHash(t, nativenames.RoleRegistry))
g.Treasury = e.CommitteeInvoker(e.NativeHash(t, nativenames.Treasury))
return g
}
// Citizen represents a registered Vita holder for testing.
type Citizen struct {
Account SingleSigner
VitaID uint64
BirthTime uint64
Registered bool
}
// RegisterCitizen registers a new Vita token for the given account.
// birthTimestamp is the citizen's birth date (Unix timestamp in milliseconds).
// Returns the Citizen with VitaID populated.
func (g *GovernmentHelper) RegisterCitizen(account SingleSigner, birthTimestamp uint64) *Citizen {
citizen := &Citizen{
Account: account,
BirthTime: birthTimestamp,
}
// Get current token count to predict the new VitaID
g.Vita.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
count, err := stack[0].TryInteger()
require.NoError(t, err)
citizen.VitaID = count.Uint64()
}, "totalSupply")
// Register the Vita token
g.Vita.Invoke(g.t, true, "register", account.ScriptHash(), birthTimestamp)
citizen.Registered = true
return citizen
}
// RegisterAdultCitizen registers a citizen who is 25 years old (adult).
// Useful for tests requiring voting age or adult status.
func (g *GovernmentHelper) RegisterAdultCitizen(account SingleSigner) *Citizen {
// 25 years ago in milliseconds
birthTime := uint64(time.Now().AddDate(-25, 0, 0).UnixMilli())
return g.RegisterCitizen(account, birthTime)
}
// RegisterChildCitizen registers a citizen who is 10 years old (child).
// Useful for tests verifying age restrictions.
func (g *GovernmentHelper) RegisterChildCitizen(account SingleSigner) *Citizen {
// 10 years ago in milliseconds
birthTime := uint64(time.Now().AddDate(-10, 0, 0).UnixMilli())
return g.RegisterCitizen(account, birthTime)
}
// RegisterElderCitizen registers a citizen who is 70 years old (elder).
// Useful for tests involving retirement age.
func (g *GovernmentHelper) RegisterElderCitizen(account SingleSigner) *Citizen {
// 70 years ago in milliseconds
birthTime := uint64(time.Now().AddDate(-70, 0, 0).UnixMilli())
return g.RegisterCitizen(account, birthTime)
}
// NewCitizenAccount creates a new account and registers it as a citizen.
// Returns both the citizen and their account for signing transactions.
func (g *GovernmentHelper) NewCitizenAccount() *Citizen {
acc := g.Vita.NewAccount(g.t).(SingleSigner)
return g.RegisterAdultCitizen(acc)
}
// VerifyVitaOwnership checks that the given account owns a Vita token.
func (g *GovernmentHelper) VerifyVitaOwnership(account util.Uint160) bool {
var hasVita bool
g.Vita.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
balance, err := stack[0].TryInteger()
require.NoError(t, err)
hasVita = balance.Cmp(big.NewInt(0)) > 0
}, "balanceOf", account)
return hasVita
}
// GetVitaID returns the Vita token ID for the given owner, or -1 if not found.
func (g *GovernmentHelper) GetVitaID(owner util.Uint160) int64 {
var vitaID int64 = -1
g.Vita.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
if stack[0].Type() != stackitem.AnyT {
tokens := stack[0].Value().([]stackitem.Item)
if len(tokens) > 0 {
id, err := tokens[0].TryInteger()
require.NoError(t, err)
vitaID = id.Int64()
}
}
}, "tokensOf", owner)
return vitaID
}
// SuspendVita suspends a citizen's Vita token (requires committee + Lex restriction).
func (g *GovernmentHelper) SuspendVita(vitaID uint64, reason string) {
// First, create a Lex liberty restriction (required for due process)
// This would normally require a judicial order
g.Vita.Invoke(g.t, true, "suspend", vitaID, reason)
}
// TransferVTS transfers VTS tokens between accounts.
func (g *GovernmentHelper) TransferVTS(from, to SingleSigner, amount int64) util.Uint256 {
vtsInvoker := g.VTS.WithSigners(from)
return vtsInvoker.Invoke(g.t, true, "transfer", from.ScriptHash(), to.ScriptHash(), amount, nil)
}
// GetVTSBalance returns the VTS balance for an account.
func (g *GovernmentHelper) GetVTSBalance(account util.Uint160) *big.Int {
var balance *big.Int
g.VTS.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
var err error
balance, err = stack[0].TryInteger()
require.NoError(t, err)
}, "balanceOf", account)
return balance
}
// MintVTS mints VTS tokens to an account (committee only).
func (g *GovernmentHelper) MintVTS(to util.Uint160, amount int64) {
g.VTS.Invoke(g.t, true, "mint", to, amount)
}
// CreateProposal creates a new Eligere proposal.
// Returns the proposal ID.
func (g *GovernmentHelper) CreateProposal(proposer *Citizen, title, description string, category int64) uint64 {
eligereInvoker := g.Eligere.WithSigners(proposer.Account)
var proposalID uint64
eligereInvoker.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
id, err := stack[0].TryInteger()
require.NoError(t, err)
proposalID = id.Uint64()
}, "createProposal", title, description, category)
return proposalID
}
// Vote casts a vote on a proposal.
func (g *GovernmentHelper) Vote(voter *Citizen, proposalID uint64, vote int64) {
eligereInvoker := g.Eligere.WithSigners(voter.Account)
eligereInvoker.Invoke(g.t, true, "vote", proposalID, vote)
}
// PrepareVitaRegistration prepares a Vita registration transaction without executing.
// Useful for batching or testing transaction ordering.
func (g *GovernmentHelper) PrepareVitaRegistration(account SingleSigner, birthTimestamp uint64) *transaction.Transaction {
return g.Vita.PrepareInvoke(g.t, "register", account.ScriptHash(), birthTimestamp)
}
// BatchRegisterCitizens registers multiple citizens in a single block.
func (g *GovernmentHelper) BatchRegisterCitizens(accounts []SingleSigner) []*Citizen {
citizens := make([]*Citizen, len(accounts))
txs := make([]*transaction.Transaction, len(accounts))
birthTime := uint64(time.Now().AddDate(-25, 0, 0).UnixMilli())
// Get starting VitaID
var startID uint64
g.Vita.InvokeAndCheck(g.t, func(t testing.TB, stack []stackitem.Item) {
count, err := stack[0].TryInteger()
require.NoError(t, err)
startID = count.Uint64()
}, "totalSupply")
// Prepare all transactions
for i, acc := range accounts {
citizens[i] = &Citizen{
Account: acc,
BirthTime: birthTime,
VitaID: startID + uint64(i),
}
txs[i] = g.Vita.PrepareInvoke(g.t, "register", acc.ScriptHash(), birthTime)
}
// Execute all in one block
g.Vita.AddNewBlock(g.t, txs...)
// Verify all succeeded
for i, tx := range txs {
g.Vita.CheckHalt(g.t, tx.Hash(), stackitem.Make(true))
citizens[i].Registered = true
}
return citizens
}

View File

@ -1,226 +0,0 @@
package tutustest
import (
"testing"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// Standard role IDs matching role_registry.go and role_registry_domain.go
const (
// Basic roles
RoleAdmin uint64 = 1
RoleValidator uint64 = 2
RoleNotary uint64 = 3
RoleOracle uint64 = 4
RoleStateRoot uint64 = 5
RoleNeoFSInner uint64 = 6
RoleNeoFSOuter uint64 = 7
RoleP2PNotary uint64 = 8
// Government service roles
RoleEducator uint64 = 20
RoleHealthProvider uint64 = 21
RoleLifeCoach uint64 = 22
RoleTributeAdmin uint64 = 23
RoleOpusSupervisor uint64 = 24
RolePalamAuditor uint64 = 25
RolePalamJudge uint64 = 26
RoleBridgeOperator uint64 = 27
RoleInvestmentMgr uint64 = 28
RoleJudge uint64 = 29
// Domain committee roles (CRIT-002)
RoleCommitteeLegal uint64 = 100
RoleCommitteeHealth uint64 = 101
RoleCommitteeEducation uint64 = 102
RoleCommitteeEconomy uint64 = 103
RoleCommitteeIdentity uint64 = 104
RoleCommitteeGovernance uint64 = 105
)
// RoleHelper provides utilities for testing RoleRegistry operations.
type RoleHelper struct {
*Executor
t testing.TB
RoleReg *ContractInvoker
Designate *ContractInvoker
}
// NewRoleHelper creates a helper for testing role-based operations.
func NewRoleHelper(t testing.TB, e *Executor) *RoleHelper {
return &RoleHelper{
Executor: e,
t: t,
RoleReg: e.CommitteeInvoker(e.NativeHash(t, nativenames.RoleRegistry)),
Designate: e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation)),
}
}
// GrantRole grants a role to an account (requires committee authority).
func (r *RoleHelper) GrantRole(account util.Uint160, roleID uint64) {
r.RoleReg.Invoke(r.t, true, "grantRole", account, roleID)
}
// RevokeRole revokes a role from an account (requires committee authority).
func (r *RoleHelper) RevokeRole(account util.Uint160, roleID uint64) {
r.RoleReg.Invoke(r.t, true, "revokeRole", account, roleID)
}
// HasRole checks if an account has a specific role.
func (r *RoleHelper) HasRole(account util.Uint160, roleID uint64) bool {
var hasRole bool
r.RoleReg.InvokeAndCheck(r.t, func(t testing.TB, stack []stackitem.Item) {
hasRole = stack[0].Value().(bool)
}, "hasRole", account, roleID)
return hasRole
}
// RequireRole asserts that an account has a specific role.
func (r *RoleHelper) RequireRole(account util.Uint160, roleID uint64) {
require.True(r.t, r.HasRole(account, roleID), "account should have role %d", roleID)
}
// RequireNoRole asserts that an account does not have a specific role.
func (r *RoleHelper) RequireNoRole(account util.Uint160, roleID uint64) {
require.False(r.t, r.HasRole(account, roleID), "account should not have role %d", roleID)
}
// GetRoleMembers returns all members with a specific role.
func (r *RoleHelper) GetRoleMembers(roleID uint64) []util.Uint160 {
var members []util.Uint160
r.RoleReg.InvokeAndCheck(r.t, func(t testing.TB, stack []stackitem.Item) {
arr, ok := stack[0].Value().([]stackitem.Item)
if !ok {
return
}
for _, item := range arr {
bs, err := item.TryBytes()
require.NoError(t, err)
members = append(members, util.Uint160(bs))
}
}, "getRoleMembers", roleID)
return members
}
// SetupEducator grants the Educator role to an account.
func (r *RoleHelper) SetupEducator(account util.Uint160) {
r.GrantRole(account, RoleEducator)
}
// SetupHealthProvider grants the HealthProvider role to an account.
func (r *RoleHelper) SetupHealthProvider(account util.Uint160) {
r.GrantRole(account, RoleHealthProvider)
}
// SetupJudge grants the Judge role to an account.
func (r *RoleHelper) SetupJudge(account util.Uint160) {
r.GrantRole(account, RoleJudge)
}
// SetupLifeCoach grants the LifeCoach role to an account.
func (r *RoleHelper) SetupLifeCoach(account util.Uint160) {
r.GrantRole(account, RoleLifeCoach)
}
// SetupTributeAdmin grants the TributeAdmin role to an account.
func (r *RoleHelper) SetupTributeAdmin(account util.Uint160) {
r.GrantRole(account, RoleTributeAdmin)
}
// SetupOpusSupervisor grants the OpusSupervisor role to an account.
func (r *RoleHelper) SetupOpusSupervisor(account util.Uint160) {
r.GrantRole(account, RoleOpusSupervisor)
}
// SetupPalamAuditor grants the PalamAuditor role to an account.
func (r *RoleHelper) SetupPalamAuditor(account util.Uint160) {
r.GrantRole(account, RolePalamAuditor)
}
// SetupPalamJudge grants the PalamJudge role to an account.
func (r *RoleHelper) SetupPalamJudge(account util.Uint160) {
r.GrantRole(account, RolePalamJudge)
}
// SetupBridgeOperator grants the BridgeOperator role to an account.
func (r *RoleHelper) SetupBridgeOperator(account util.Uint160) {
r.GrantRole(account, RoleBridgeOperator)
}
// SetupInvestmentManager grants the InvestmentManager role to an account.
func (r *RoleHelper) SetupInvestmentManager(account util.Uint160) {
r.GrantRole(account, RoleInvestmentMgr)
}
// SetupDomainCommittee grants a domain committee role to an account.
func (r *RoleHelper) SetupDomainCommittee(account util.Uint160, domain string) {
var roleID uint64
switch domain {
case "legal":
roleID = RoleCommitteeLegal
case "health":
roleID = RoleCommitteeHealth
case "education":
roleID = RoleCommitteeEducation
case "economy":
roleID = RoleCommitteeEconomy
case "identity":
roleID = RoleCommitteeIdentity
case "governance":
roleID = RoleCommitteeGovernance
default:
require.Fail(r.t, "unknown domain: %s", domain)
}
r.GrantRole(account, roleID)
}
// RoleScenario represents a test scenario with role assignments.
type RoleScenario struct {
helper *RoleHelper
accounts map[string]SingleSigner
roles map[string][]uint64
}
// NewRoleScenario creates a new role scenario builder.
func (r *RoleHelper) NewScenario() *RoleScenario {
return &RoleScenario{
helper: r,
accounts: make(map[string]SingleSigner),
roles: make(map[string][]uint64),
}
}
// WithAccount adds a named account to the scenario.
func (s *RoleScenario) WithAccount(name string, acc SingleSigner) *RoleScenario {
s.accounts[name] = acc
return s
}
// WithRole assigns a role to a named account.
func (s *RoleScenario) WithRole(accountName string, roleID uint64) *RoleScenario {
s.roles[accountName] = append(s.roles[accountName], roleID)
return s
}
// Setup executes all role assignments in the scenario.
func (s *RoleScenario) Setup() map[string]SingleSigner {
for name, roles := range s.roles {
acc, ok := s.accounts[name]
require.True(s.helper.t, ok, "account %s not found", name)
for _, roleID := range roles {
s.helper.GrantRole(acc.ScriptHash(), roleID)
}
}
return s.accounts
}
// Get returns a named account from the scenario.
func (s *RoleScenario) Get(name string) SingleSigner {
acc, ok := s.accounts[name]
require.True(s.helper.t, ok, "account %s not found", name)
return acc
}