solo

package
v0.6.1-alpha.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2023 License: Apache-2.0 Imports: 70 Imported by: 9

README

Package Solo

Package solo is a development tool for writing unit tests for IOTA Smart Contracts (ISC).

The package is intended for developers of smart contracts as well as contributors to the development of the ISC and the Wasp node itself.

Normally, the smart contract is developed and tested in the solo environment before trying it out on the network of Wasp nodes. Running and testing the smart contract on 'solo' does not require to run the Wasp nodes nor committee of nodes: just ordinary 'go test' environment. Same time, the smart contracts in solo is run in native environment, including transactions, tokens, signatures and virtual state access. This allows deployment of smart contracts on Wasp network without any changes.

See here the GoDoc documentation of the solo package: Go Reference

Documentation

Overview

Package 'solo' is a development tool to write unit tests for IOTA Smart Contracts (ISC).

A development tool

The package is intended for developers of smart contracts as well as contributors to the development of the ISC and the Wasp node itself.

Normally, the smart contract is developed and tested in the 'solo' environment before trying it out on the network of Wasp nodes. Running and testing the smart contract on 'solo' does not require to run the Wasp nodes nor committee of nodes: just ordinary 'go test' environment.

Native environment

'solo' shares the same code of Virtual Machine with the Wasp node. This guarantees that smart contract programs can later be deployed on chains which are run by the network of Wasp nodes without any modifications.

The 'solo' environment uses in-memory UTXO ledger to validate and store transactions. The UTXODB mocks Goshimmer UTXO ledger, it uses same value transaction structure, colored tokens, signature schemes as well as transaction and signature validation as in Value Tangle of Goshimmer (Pollen release). The only difference with the Value Tangle is that UTXODB provides full synchronicity of ledger updates.

The virtual state (key/value database) in 'solo' is an in-memory database. It provides exactly the same interface of access to it as the database of the Wasp node.

Writing smart contracts

The smart contracts are usually written in Rust using Rust libraries provided in the 'wasplib' repository at https://github.com/iotaledger/wasplib. Rust code is compiled into the WebAssembly (Wasm) binary. The Wasm binary is uploaded by 'solo' onto the chain and then loaded into the VM and executed.

Another option to write and run ISC smart contracts is to use the native Go environment of the Wasp node and 'Sandbox' interface, provided by the Wasp for the VM: the "hardcoded" mode. The latter approach is not normally used to develop apps, however is used for the 4 builtin contracts which constitutes the core of the ISC chains. The approach to write "hardcoded" smart contracts may also be very useful for the development and debugging of the smart contract logic in IDE such as GoLand, before writing it as a Rust/Wasm smart contract.

Example test

The following example deploys chain and retrieves basic info from the deployed chain. It is expected 4 core contracts deployed on it by default and the test prints them.

func TestSolo1(t *testing.T) {
  env := solo.New(t, false, false)
  chain := env.NewChain(nil, "ex1")

  chainInfo, coreContracts := chain.GetInfo()   // calls view root::GetInfo
  require.EqualValues(t, 4, len(coreContracts)) // 4 core contracts deployed by default

  t.Logf("chainID: %s", chainInfo.ChainID)
  t.Logf("chain owner ID: %s", chainInfo.ChainOwnerID)
  for hname, rec := range coreContracts {
     t.Logf("    Core contract '%s': %s", rec.Name, isc.NewContractID(chain.ChainID, hname))
  }
}

will produce the following output:

      === RUN   TestSolo1
 34:37.415	INFO	TestSolo1	solo/solo.go:153	deploying new chain 'ex1'
	34:37.419	INFO	TestSolo1.ex1	vmcontext/runreq.go:177	eventlog -> '[req] [0]Ei4d6oUbcgSPnmpTupeLaTNoNf1hRu8ZfZfmw2KFKzZm: Ok'
	34:37.420	INFO	TestSolo1.ex1	solo/run.go:75	state transition #0 --> #1. Requests in the block: 1. Posted: 0
	34:37.420	INFO	TestSolo1	solo/clock.go:44	ClockStep: logical clock advanced by 1ms
	34:37.420	INFO	TestSolo1.ex1	solo/solo.go:233	chain 'ex1' deployed. Chain ID: aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3
	34:37.420	INFO	TestSolo1.ex1	solo/req.go:145	callView: root::getChainInfo
	solo_test.go:18: chainID: aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3
	solo_test.go:19: chain owner ID: A/UrYEv4Yh7WU1M29cKq73tb2CUx8EYXfJt6JZn5srw19U
	solo_test.go:21:     Core contract 'accounts': aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3::3c4b5e02
	solo_test.go:21:     Core contract 'blob': aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3::fd91bc63
	solo_test.go:21:     Core contract 'root': aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3::cebf5908
	solo_test.go:21:     Core contract 'eventlog': aEbE2vX6jrGhQ3AKHCPmQmn2qa11CpCRzaEgtVJRAje3::661aa7d8
	--- PASS: TestSolo1 (0.01s)

Index

Constants

View Source
const (
	DestroyTokensGasBudgetBaseTokens       = 1 * isc.Million
	SendToL2AccountGasBudgetBaseTokens     = 1 * isc.Million
	DestroyFoundryGasBudgetBaseTokens      = 1 * isc.Million
	TransferAllowanceToGasBudgetBaseTokens = 1 * isc.Million
)

CreateFoundryGasBudgetBaseTokens always takes 100000 base tokens as gas budget and ftokens for the call

View Source
const (
	MaxRequestsInBlock = 100
)

Variables

This section is empty.

Functions

func NewEthereumAccount added in v0.3.0

func NewEthereumAccount() (*ecdsa.PrivateKey, common.Address)

func NewIscRequestFromCallParams added in v0.3.0

func NewIscRequestFromCallParams(ch *Chain, req *CallParams, keyPair *cryptolib.KeyPair) (isc.Request, error)

Warning: if the same `req` is passed in different occasions, the resulting request will have different IDs (because the ledger state is different)

Types

type CallParams

type CallParams struct {
	// contains filtered or unexported fields
}

func NewCallParams

func NewCallParams(scName, funName string, params ...interface{}) *CallParams

NewCallParams creates structure which wraps in one object call parameters, used in PostRequestSync and callViewFull calls:

  • 'scName' is a name of the target smart contract
  • 'funName' is a name of the target entry point (the function) of the smart contract program
  • 'params' is either a dict.Dict, or a sequence of pairs 'paramName', 'paramValue' which constitute call parameters The 'paramName' must be a string and 'paramValue' must different types (encoded based on type)

With the WithTransfers the CallParams structure may be complemented with attached ftokens sent together with the request

func NewCallParamsFromDict added in v0.3.0

func NewCallParamsFromDict(scName, funName string, par dict.Dict) *CallParams

func NewCallParamsFromDictByHname added in v0.3.0

func NewCallParamsFromDictByHname(hContract, hFunction isc.Hname, par dict.Dict) *CallParams

func (*CallParams) AddAllowance added in v0.3.0

func (r *CallParams) AddAllowance(allowance *isc.Assets) *CallParams

func (*CallParams) AddAllowanceBaseTokens added in v0.3.0

func (r *CallParams) AddAllowanceBaseTokens(amount uint64) *CallParams

func (*CallParams) AddAllowanceNFTs added in v0.3.0

func (r *CallParams) AddAllowanceNFTs(nfts ...iotago.NFTID) *CallParams

func (*CallParams) AddAllowanceNativeTokens added in v0.3.0

func (r *CallParams) AddAllowanceNativeTokens(nativeTokenID iotago.NativeTokenID, amount interface{}) *CallParams

func (*CallParams) AddAllowanceNativeTokensVect added in v0.3.0

func (r *CallParams) AddAllowanceNativeTokensVect(nativeTokens ...*iotago.NativeToken) *CallParams

func (*CallParams) AddBaseTokens added in v0.3.0

func (r *CallParams) AddBaseTokens(amount uint64) *CallParams

func (*CallParams) AddFungibleTokens added in v0.3.0

func (r *CallParams) AddFungibleTokens(assets *isc.Assets) *CallParams

func (*CallParams) AddNativeTokens added in v0.3.0

func (r *CallParams) AddNativeTokens(nativeTokenID iotago.NativeTokenID, amount interface{}) *CallParams

func (*CallParams) AddNativeTokensVect added in v0.3.0

func (r *CallParams) AddNativeTokensVect(nativeTokens ...*iotago.NativeToken) *CallParams

func (*CallParams) GasBudget added in v0.3.0

func (r *CallParams) GasBudget() uint64

func (*CallParams) NewRequestOffLedger added in v0.2.0

func (r *CallParams) NewRequestOffLedger(chainID isc.ChainID, keyPair *cryptolib.KeyPair) isc.OffLedgerRequest

NewRequestOffLedger creates off-ledger request from parameters

func (*CallParams) WithAllowance added in v0.3.0

func (r *CallParams) WithAllowance(allowance *isc.Assets) *CallParams

func (*CallParams) WithFungibleTokens added in v0.3.0

func (r *CallParams) WithFungibleTokens(assets *isc.Assets) *CallParams

func (*CallParams) WithGasBudget added in v0.3.0

func (r *CallParams) WithGasBudget(gasBudget uint64) *CallParams

func (*CallParams) WithMaxAffordableGasBudget added in v0.3.0

func (r *CallParams) WithMaxAffordableGasBudget() *CallParams

func (*CallParams) WithNFT added in v0.3.0

func (r *CallParams) WithNFT(nft *isc.NFT) *CallParams

Adds an nft to be sent (only applicable when the call is made via on-ledger request)

func (*CallParams) WithNonce added in v0.3.0

func (r *CallParams) WithNonce(nonce uint64) *CallParams

func (*CallParams) WithSender added in v0.3.0

func (r *CallParams) WithSender(sender iotago.Address) *CallParams

type Chain

type Chain struct {
	// Env is a pointer to the global structure of the 'solo' test
	Env *Solo

	// Name is the name of the chain
	Name string

	// StateControllerKeyPair signature scheme of the chain address, the one used to control funds owned by the chain.
	// In Solo it is Ed25519 signature scheme (in full Wasp environment is is a BLS address)
	StateControllerKeyPair *cryptolib.KeyPair
	StateControllerAddress iotago.Address

	// ChainID is the ID of the chain (in this version alias of the ChainAddress)
	ChainID isc.ChainID

	// OriginatorPrivateKey the key pair used to create the chain (origin transaction).
	// It is a default key pair in many of Solo calls which require private key.
	OriginatorPrivateKey *cryptolib.KeyPair
	OriginatorAddress    iotago.Address
	// OriginatorAgentID is the OriginatorAddress represented in the form of AgentID
	OriginatorAgentID isc.AgentID

	// ValidatorFeeTarget is the agent ID to which all fees are accrued. By default, it is equal to OriginatorAgentID
	ValidatorFeeTarget isc.AgentID

	RequestsBlock     uint32
	RequestsRemaining int
	// contains filtered or unexported fields
}

Chain represents state of individual chain. There may be several parallel instances of the chain in the 'solo' test

func (*Chain) AddAllowedStateController added in v0.2.0

func (ch *Chain) AddAllowedStateController(addr iotago.Address, keyPair *cryptolib.KeyPair) error

AddAllowedStateController adds the address to the allowed state controlled address list

func (*Chain) AssertControlAddresses added in v0.3.0

func (ch *Chain) AssertControlAddresses()

func (*Chain) AssertL2BaseTokens added in v0.3.0

func (ch *Chain) AssertL2BaseTokens(agentID isc.AgentID, bal uint64)

func (*Chain) AssertL2NativeTokens added in v0.3.0

func (ch *Chain) AssertL2NativeTokens(agentID isc.AgentID, nativeTokenID iotago.NativeTokenID, bal interface{})

func (*Chain) AssertL2TotalBaseTokens added in v0.3.0

func (ch *Chain) AssertL2TotalBaseTokens(bal uint64)

func (*Chain) AssertL2TotalNativeTokens added in v0.3.0

func (ch *Chain) AssertL2TotalNativeTokens(nativeTokenID iotago.NativeTokenID, bal interface{})

func (*Chain) AttachToRequestProcessed added in v0.3.7

func (*Chain) AttachToRequestProcessed(func(isc.RequestID)) context.CancelFunc

AttachToRequestProcessed implements chain.Chain

func (*Chain) AwaitRequestProcessed added in v1.0.3

func (*Chain) AwaitRequestProcessed(ctx context.Context, requestID isc.RequestID, confirmed bool) <-chan *blocklog.RequestReceipt

AwaitRequestProcessed implements chain.Chain

func (*Chain) CallView

func (ch *Chain) CallView(scName, funName string, params ...interface{}) (dict.Dict, error)

CallView calls the view entry point of the smart contract. The call params should be either a dict.Dict, or pairs of ('paramName', 'paramValue') where 'paramName' is a string and 'paramValue' must be of type accepted by the 'codec' package

func (*Chain) CallViewAtState added in v1.0.3

func (ch *Chain) CallViewAtState(chainState state.State, scName, funName string, params ...interface{}) (dict.Dict, error)

func (*Chain) CallViewByHname added in v0.3.0

func (ch *Chain) CallViewByHname(hContract, hFunction isc.Hname, params ...interface{}) (dict.Dict, error)

func (*Chain) CallViewByHnameAtState

func (ch *Chain) CallViewByHnameAtState(chainState state.State, hContract, hFunction isc.Hname, params ...interface{}) (dict.Dict, error)

func (*Chain) CheckAccountLedger

func (ch *Chain) CheckAccountLedger()

CheckAccountLedger check integrity of the on-chain ledger. Sum of all accounts must be equal to total ftokens

func (*Chain) CheckChain

func (ch *Chain) CheckChain()

CheckChain checks fundamental integrity of the chain

func (*Chain) CommonAccount added in v0.2.0

func (ch *Chain) CommonAccount() isc.AgentID

CommonAccount return the agentID of the common account (controlled by the owner)

func (*Chain) ConfigUpdated added in v1.0.3

func (*Chain) ConfigUpdated(accessNodes []*cryptolib.PublicKey)

ConfigUpdated implements chain.Chain

func (*Chain) ContractAgentID added in v0.2.0

func (ch *Chain) ContractAgentID(name string) isc.AgentID

func (*Chain) DeployContract

func (ch *Chain) DeployContract(user *cryptolib.KeyPair, name string, programHash hashing.HashValue, params ...interface{}) error

DeployContract deploys contract with the given name by its 'programHash'. 'sigScheme' represents the private key of the creator (nil defaults to chain originator). The 'creator' becomes an immutable property of the contract instance. The parameter 'programHash' can be one of the following:

  • it is and ID of the blob stored on the chain in the format of Wasm binary
  • it can be a hash (ID) of the example smart contract ("hardcoded"). The "hardcoded" smart contract must be made available with the call examples.AddProcessor

func (*Chain) DeployWasmContract

func (ch *Chain) DeployWasmContract(keyPair *cryptolib.KeyPair, name, fname string, params ...interface{}) error

DeployWasmContract is syntactic sugar for uploading Wasm binary from file and deploying the smart contract in one call

func (*Chain) DepositAssetsToL2 added in v0.3.0

func (ch *Chain) DepositAssetsToL2(assets *isc.Assets, user *cryptolib.KeyPair) error

DepositAssetsToL2 deposits ftokens on user's on-chain account

func (*Chain) DepositBaseTokensToL2 added in v0.3.0

func (ch *Chain) DepositBaseTokensToL2(amount uint64, user *cryptolib.KeyPair) error

DepositBaseTokensToL2 deposits ftokens on user's on-chain account

func (*Chain) DepositNFT added in v1.0.3

func (ch *Chain) DepositNFT(nft *isc.NFT, to isc.AgentID, owner *cryptolib.KeyPair) error

func (*Chain) DestroyFoundry added in v0.3.0

func (ch *Chain) DestroyFoundry(sn uint32, user *cryptolib.KeyPair) error

func (*Chain) DestroyTokensOnL1 added in v0.3.0

func (ch *Chain) DestroyTokensOnL1(nativeTokenID iotago.NativeTokenID, amount interface{}, user *cryptolib.KeyPair) error

DestroyTokensOnL1 sends tokens as ftokens and destroys in the same transaction

func (*Chain) DestroyTokensOnL2 added in v0.3.0

func (ch *Chain) DestroyTokensOnL2(nativeTokenID iotago.NativeTokenID, amount interface{}, user *cryptolib.KeyPair) error

DestroyTokensOnL2 destroys tokens (identified by foundry SN) on user's on-chain account

func (*Chain) DumpAccounts

func (ch *Chain) DumpAccounts() string

DumpAccounts dumps all account balances into the human-readable string

func (*Chain) EVM added in v0.3.0

func (ch *Chain) EVM() *jsonrpc.EVMChain

func (*Chain) EVMGasRatio added in v0.3.0

func (ch *Chain) EVMGasRatio() util.Ratio32

func (*Chain) EnqueueAliasOutput added in v0.3.0

func (ch *Chain) EnqueueAliasOutput(_ *isc.AliasOutputWithID)

func (*Chain) EnqueueDismissChain added in v0.3.0

func (ch *Chain) EnqueueDismissChain(_ string)

func (*Chain) EstimateGasOffLedger added in v0.3.0

func (ch *Chain) EstimateGasOffLedger(req *CallParams, keyPair *cryptolib.KeyPair, useMaxBalance ...bool) (gas, gasFee uint64, err error)

EstimateGasOffLedger executes the given on-ledger request without committing any changes in the ledger. It returns the amount of gas consumed. if useFakeBalance is `true` the request will be executed as if the sender had enough base tokens to cover the maximum gas allowed WARNING: Gas estimation is just an "estimate", there is no guarantees that the real call will bear the same cost, due to the turing-completeness of smart contracts

func (*Chain) EstimateGasOnLedger added in v0.3.0

func (ch *Chain) EstimateGasOnLedger(req *CallParams, keyPair *cryptolib.KeyPair, useFakeBudget ...bool) (gas, gasFee uint64, err error)

EstimateGasOnLedger executes the given on-ledger request without committing any changes in the ledger. It returns the amount of gas consumed. if useFakeBalance is `true` the request will be executed as if the sender had enough base tokens to cover the maximum gas allowed WARNING: Gas estimation is just an "estimate", there is no guarantees that the real call will bear the same cost, due to the turing-completeness of smart contracts

func (*Chain) EstimateNeededStorageDeposit added in v0.3.0

func (ch *Chain) EstimateNeededStorageDeposit(req *CallParams, keyPair *cryptolib.KeyPair) uint64

EstimateNeededStorageDeposit estimates the amount of base tokens that will be needed to add to the request (if any) in order to cover for the storage deposit.

func (*Chain) FindContract

func (ch *Chain) FindContract(scName string) (*root.ContractRecord, error)

FindContract is a view call to the 'root' smart contract on the chain. It returns blobCache record of the deployed smart contract with the given name

func (*Chain) GetAllowedStateControllerAddresses added in v0.2.0

func (ch *Chain) GetAllowedStateControllerAddresses() []iotago.Address

AddAllowedStateController adds the address to the allowed state controlled address list

func (*Chain) GetAnchorOutput added in v0.3.0

func (ch *Chain) GetAnchorOutput() *isc.AliasOutputWithID

func (*Chain) GetBlobInfo

func (ch *Chain) GetBlobInfo(blobHash hashing.HashValue) (map[string]uint32, bool)

GetBlobInfo return info about blob with the given hash with existence flag The blob information is returned as a map of pairs 'blobFieldName': 'fieldDataLength'

func (*Chain) GetBlockInfo added in v0.2.0

func (ch *Chain) GetBlockInfo(blockIndex ...uint32) (*blocklog.BlockInfo, error)

GetBlockInfo return BlockInfo for the particular block index in the chain

func (*Chain) GetBlockProof added in v0.3.0

func (ch *Chain) GetBlockProof(blockIndex uint32) (*blocklog.BlockInfo, *trie.MerkleProof, error)

GetBlockProof returns Merkle proof of the key in the state

func (*Chain) GetCandidateNodes added in v0.3.0

func (ch *Chain) GetCandidateNodes() []*governance.AccessNodeInfo

func (*Chain) GetChainMetrics added in v1.0.3

func (*Chain) GetChainMetrics() metrics.IChainMetrics

GetChainMetrics implements chain.Chain

func (*Chain) GetChainNodes added in v0.3.0

func (ch *Chain) GetChainNodes() []peering.PeerStatusProvider

func (*Chain) GetCommitteeInfo added in v0.3.0

func (ch *Chain) GetCommitteeInfo() *chain.CommitteeInfo

func (*Chain) GetConsensusPipeMetrics added in v0.3.7

func (*Chain) GetConsensusPipeMetrics() chain.ConsensusPipeMetrics

GetConsensusPipeMetrics implements chain.Chain

func (*Chain) GetConsensusWorkflowStatus added in v0.3.7

func (*Chain) GetConsensusWorkflowStatus() chain.ConsensusWorkflowStatus

GetConsensusWorkflowStatus implements chain.Chain

func (*Chain) GetContractStateCommitment added in v0.3.0

func (ch *Chain) GetContractStateCommitment(hn isc.Hname) ([]byte, error)

GetContractStateCommitment returns commitment to the state of the specific contract, if possible

func (*Chain) GetControlAddresses added in v0.2.0

func (ch *Chain) GetControlAddresses() *blocklog.ControlAddresses

func (*Chain) GetErrorMessageFormat added in v0.3.0

func (ch *Chain) GetErrorMessageFormat(code isc.VMErrorCode) (string, error)

func (*Chain) GetEventsForBlock added in v0.2.0

func (ch *Chain) GetEventsForBlock(blockIndex uint32) ([]string, error)

GetEventsForBlock calls the view in the 'blocklog' core smart contract to retrieve events for a given block.

func (*Chain) GetEventsForContract added in v0.2.0

func (ch *Chain) GetEventsForContract(name string) ([]string, error)

GetEventsForContract calls the view in the 'blocklog' core smart contract to retrieve events for a given smart contract.

func (*Chain) GetEventsForRequest added in v0.2.0

func (ch *Chain) GetEventsForRequest(reqID isc.RequestID) ([]string, error)

GetEventsForRequest calls the view in the 'blocklog' core smart contract to retrieve events for a given request.

func (*Chain) GetFoundryOutput added in v0.3.0

func (ch *Chain) GetFoundryOutput(sn uint32) (*iotago.FoundryOutput, error)

func (*Chain) GetGasFeePolicy added in v0.3.0

func (ch *Chain) GetGasFeePolicy() *gas.FeePolicy

func (*Chain) GetGasLimits added in v1.0.3

func (ch *Chain) GetGasLimits() *gas.Limits

func (*Chain) GetInfo

func (ch *Chain) GetInfo() (isc.ChainID, isc.AgentID, map[isc.Hname]*root.ContractRecord)

GetInfo return main parameters of the chain:

  • chainID
  • agentID of the chain owner
  • blobCache of contract deployed on the chain in the form of map 'contract hname': 'contract record'

func (*Chain) GetL1Commitment added in v0.3.0

func (ch *Chain) GetL1Commitment() *state.L1Commitment

GetL1Commitment returns state commitment taken from the anchor output

func (*Chain) GetL2FundsFromFaucet added in v0.3.0

func (ch *Chain) GetL2FundsFromFaucet(agentID isc.AgentID, baseTokens ...uint64)

func (*Chain) GetLatestBlockInfo added in v0.2.0

func (ch *Chain) GetLatestBlockInfo() *blocklog.BlockInfo

GetLatestBlockInfo return BlockInfo for the latest block in the chain

func (*Chain) GetMerkleProof added in v0.3.0

func (ch *Chain) GetMerkleProof(scHname isc.Hname, key []byte) *trie.MerkleProof

GetMerkleProof return the merkle proof of the key in the smart contract. Assumes Merkle model is used

func (*Chain) GetMerkleProofRaw added in v0.3.0

func (ch *Chain) GetMerkleProofRaw(key []byte) *trie.MerkleProof

GetMerkleProofRaw returns Merkle proof of the key in the state

func (*Chain) GetNativeTokenIDByFoundrySN added in v0.3.0

func (ch *Chain) GetNativeTokenIDByFoundrySN(sn uint32) (iotago.NativeTokenID, error)

func (*Chain) GetOnChainTokenIDs added in v0.3.0

func (ch *Chain) GetOnChainTokenIDs() []iotago.NativeTokenID

func (*Chain) GetRequestIDsForBlock added in v0.2.0

func (ch *Chain) GetRequestIDsForBlock(blockIndex uint32) []isc.RequestID

GetRequestIDsForBlock returns the list of requestIDs settled in a particular block

func (*Chain) GetRequestReceipt added in v0.2.0

func (ch *Chain) GetRequestReceipt(reqID isc.RequestID) (*blocklog.RequestReceipt, error)

GetRequestReceipt gets the log records for a particular request, the block index and request index in the block

func (*Chain) GetRequestReceiptsForBlock added in v0.2.0

func (ch *Chain) GetRequestReceiptsForBlock(blockIndex ...uint32) []*blocklog.RequestReceipt

GetRequestReceiptsForBlock returns all request log records for a particular block

func (*Chain) GetRequestReceiptsForBlockRange added in v0.2.0

func (ch *Chain) GetRequestReceiptsForBlockRange(fromBlockIndex, toBlockIndex uint32) []*blocklog.RequestReceipt

GetRequestReceiptsForBlockRange returns all request log records for range of blocks, inclusively. Upper bound is 'latest block' is set to 0

func (*Chain) GetRequestReceiptsForBlockRangeAsStrings added in v0.2.0

func (ch *Chain) GetRequestReceiptsForBlockRangeAsStrings(fromBlockIndex, toBlockIndex uint32) []string

func (*Chain) GetRootCommitment added in v0.3.0

func (ch *Chain) GetRootCommitment() trie.Hash

GetRootCommitment returns the root commitment of the latest state index

func (*Chain) GetTimeData added in v0.3.7

func (*Chain) GetTimeData() time.Time

GetTimeData implements chain.Chain

func (*Chain) GetWasmBinary

func (ch *Chain) GetWasmBinary(progHash hashing.HashValue) ([]byte, error)

GetWasmBinary retrieves program binary in the format of Wasm blob from the chain by hash.

func (*Chain) GrantDeployPermission

func (ch *Chain) GrantDeployPermission(keyPair *cryptolib.KeyPair, deployerAgentID isc.AgentID) error

GrantDeployPermission gives permission to the specified agentID to deploy SCs into the chain

func (*Chain) HasL2NFT added in v0.3.0

func (ch *Chain) HasL2NFT(agentID isc.AgentID, nftID *iotago.NFTID) bool

func (*Chain) ID added in v0.3.0

func (ch *Chain) ID() isc.ChainID

func (*Chain) IsRequestProcessed added in v0.2.0

func (ch *Chain) IsRequestProcessed(reqID isc.RequestID) bool

IsRequestProcessed checks if the request is booked on the chain as processed

func (*Chain) L1L2Funds added in v0.3.0

func (ch *Chain) L1L2Funds(addr iotago.Address) *L1L2AddressAssets

func (*Chain) L2Accounts added in v0.3.0

func (ch *Chain) L2Accounts() []isc.AgentID

L2Accounts returns all accounts on the chain with non-zero balances

func (*Chain) L2Assets added in v0.3.0

func (ch *Chain) L2Assets(agentID isc.AgentID) *isc.Assets

L2Assets return all tokens contained in the on-chain account controlled by the 'agentID'

func (*Chain) L2BaseTokens added in v0.3.0

func (ch *Chain) L2BaseTokens(agentID isc.AgentID) uint64

func (*Chain) L2CommonAccountAssets added in v0.3.0

func (ch *Chain) L2CommonAccountAssets() *isc.Assets

func (*Chain) L2CommonAccountBaseTokens added in v0.3.0

func (ch *Chain) L2CommonAccountBaseTokens() uint64

func (*Chain) L2CommonAccountNativeTokens added in v0.3.0

func (ch *Chain) L2CommonAccountNativeTokens(nativeTokenID iotago.NativeTokenID) *big.Int

func (*Chain) L2Ledger added in v0.3.0

func (ch *Chain) L2Ledger() map[string]*isc.Assets

func (*Chain) L2LedgerString added in v0.3.0

func (ch *Chain) L2LedgerString() string

func (*Chain) L2NFTs added in v0.3.0

func (ch *Chain) L2NFTs(agentID isc.AgentID) []iotago.NFTID

func (*Chain) L2NativeTokens added in v0.3.0

func (ch *Chain) L2NativeTokens(agentID isc.AgentID, nativeTokenID iotago.NativeTokenID) *big.Int

func (*Chain) L2TotalAssets added in v0.3.0

func (ch *Chain) L2TotalAssets() *isc.Assets

L2TotalAssets return total sum of ftokens contained in the on-chain accounts

func (*Chain) L2TotalBaseTokens added in v0.3.0

func (ch *Chain) L2TotalBaseTokens() uint64

L2TotalBaseTokens return total sum of base tokens in L2 (all accounts)

func (*Chain) LastReceipt added in v0.3.0

func (ch *Chain) LastReceipt() *isc.Receipt

LastReceipt returns the receipt for the latest request processed by the chain, will return nil if the last block is empty

func (*Chain) LatestAliasOutput added in v1.0.3

func (ch *Chain) LatestAliasOutput(freshness chain.StateFreshness) (*isc.AliasOutputWithID, error)

LatestAliasOutput implements chain.Chain

func (*Chain) LatestBlockIndex added in v1.0.3

func (ch *Chain) LatestBlockIndex() uint32

func (*Chain) LatestState added in v1.0.3

func (ch *Chain) LatestState(freshness chain.StateFreshness) (state.State, error)

LatestState implements chain.Chain

func (*Chain) Log

func (ch *Chain) Log() *logger.Logger

func (*Chain) MintTokens added in v0.3.0

func (ch *Chain) MintTokens(foundry, amount interface{}, user *cryptolib.KeyPair) error

func (*Chain) MustDepositBaseTokensToL2 added in v0.3.0

func (ch *Chain) MustDepositBaseTokensToL2(amount uint64, user *cryptolib.KeyPair)

func (*Chain) MustDepositNFT added in v1.0.3

func (ch *Chain) MustDepositNFT(nft *isc.NFT, to isc.AgentID, owner *cryptolib.KeyPair)

func (*Chain) NewEthereumAccountWithL2Funds added in v0.3.0

func (ch *Chain) NewEthereumAccountWithL2Funds(baseTokens ...uint64) (*ecdsa.PrivateKey, common.Address)

func (*Chain) NewFoundryParams added in v0.3.0

func (ch *Chain) NewFoundryParams(maxSupply interface{}) *foundryParams

func (*Chain) PostEthereumTransaction added in v0.3.0

func (ch *Chain) PostEthereumTransaction(tx *types.Transaction) (dict.Dict, error)

func (*Chain) PostRequestOffLedger added in v0.2.0

func (ch *Chain) PostRequestOffLedger(req *CallParams, keyPair *cryptolib.KeyPair) (dict.Dict, error)

func (*Chain) PostRequestSync

func (ch *Chain) PostRequestSync(req *CallParams, keyPair *cryptolib.KeyPair) (dict.Dict, error)

PostRequestSync posts a request synchronously sent by the test program to the smart contract on the same or another chain:

  • creates a request transaction with the request block on it. The sigScheme is used to sign the inputs of the transaction or OriginatorKeyPair is used if parameter is nil
  • adds request transaction to UTXODB
  • runs the request in the VM. It results in new updated virtual state and a new transaction which anchors the state.
  • adds the resulting transaction to UTXODB
  • posts requests, contained in the resulting transaction to backlog queues of respective chains
  • returns the result of the call to the smart contract's entry point

Note that in real network of Wasp nodes (the committee) posting the transaction is completely asynchronous, i.e. result of the call is not available to the originator of the post.

Unlike the real Wasp environment, the 'solo' environment makes PostRequestSync a synchronous call. It makes it possible step-by-step debug of the smart contract logic. The call should be used only from the main thread (goroutine)

func (*Chain) PostRequestSyncExt added in v0.3.0

func (ch *Chain) PostRequestSyncExt(req *CallParams, keyPair *cryptolib.KeyPair) (*iotago.Transaction, *blocklog.RequestReceipt, dict.Dict, error)

func (*Chain) PostRequestSyncTx

func (ch *Chain) PostRequestSyncTx(req *CallParams, keyPair *cryptolib.KeyPair) (*iotago.Transaction, dict.Dict, error)

func (*Chain) Processors added in v0.3.0

func (ch *Chain) Processors() *processors.Cache

func (*Chain) ReceiveOffLedgerRequest added in v1.0.3

func (*Chain) ReceiveOffLedgerRequest(request isc.OffLedgerRequest, sender *cryptolib.PublicKey)

ReceiveOffLedgerRequest implements chain.Chain

func (*Chain) RemoveAllowedStateController added in v0.2.0

func (ch *Chain) RemoveAllowedStateController(addr iotago.Address, keyPair *cryptolib.KeyPair) error

AddAllowedStateController adds the address to the allowed state controlled address list

func (*Chain) RequestFromParamsToLedger

func (ch *Chain) RequestFromParamsToLedger(req *CallParams, keyPair *cryptolib.KeyPair) (*iotago.Transaction, isc.RequestID, error)

RequestFromParamsToLedger creates transaction with one request based on parameters and sigScheme Then it adds it to the ledger, atomically. Locking on the mutex is needed to prevent mess when several goroutines work on the same address

func (*Chain) ResolveError added in v0.3.7

func (ch *Chain) ResolveError(e *isc.UnresolvedVMError) (*isc.VMError, error)

ResolveError implements chain.Chain

func (*Chain) ResolveVMError added in v0.3.0

func (ch *Chain) ResolveVMError(e *isc.UnresolvedVMError) *isc.VMError

func (*Chain) RevokeDeployPermission

func (ch *Chain) RevokeDeployPermission(keyPair *cryptolib.KeyPair, deployerAgentID isc.AgentID) error

RevokeDeployPermission removes permission of the specified agentID to deploy SCs into the chain

func (*Chain) RotateStateController added in v0.2.0

func (ch *Chain) RotateStateController(newStateAddr iotago.Address, newStateKeyPair, ownerKeyPair *cryptolib.KeyPair) error

RotateStateController rotates the chain to the new controller address. We assume self-governed chain here. Mostly use for the testing of committee rotation logic, otherwise not much needed for smart contract testing

func (*Chain) RunOffLedgerRequest added in v0.3.0

func (ch *Chain) RunOffLedgerRequest(r isc.Request) (dict.Dict, error)

func (*Chain) RunOffLedgerRequests added in v0.3.0

func (ch *Chain) RunOffLedgerRequests(reqs []isc.Request) []*vm.RequestResult

func (*Chain) RunRequestsSync added in v0.3.0

func (ch *Chain) RunRequestsSync(reqs []isc.Request, trace string) (results []*vm.RequestResult)

func (*Chain) SendFromL1ToL2Account added in v0.3.0

func (ch *Chain) SendFromL1ToL2Account(totalBaseTokens uint64, toSend *isc.Assets, target isc.AgentID, user *cryptolib.KeyPair) error

SendFromL1ToL2Account sends ftokens from L1 address to the target account on L2 Sender pays the gas fee

func (*Chain) SendFromL1ToL2AccountBaseTokens added in v0.3.0

func (ch *Chain) SendFromL1ToL2AccountBaseTokens(totalBaseTokens, baseTokensSend uint64, target isc.AgentID, user *cryptolib.KeyPair) error

func (*Chain) SendFromL2ToL2Account added in v0.3.0

func (ch *Chain) SendFromL2ToL2Account(transfer *isc.Assets, target isc.AgentID, user *cryptolib.KeyPair) error

SendFromL2ToL2Account moves ftokens on L2 from user's account to the target

func (*Chain) SendFromL2ToL2AccountBaseTokens added in v0.3.0

func (ch *Chain) SendFromL2ToL2AccountBaseTokens(baseTokens uint64, target isc.AgentID, user *cryptolib.KeyPair) error

func (*Chain) SendFromL2ToL2AccountNativeTokens added in v0.3.0

func (ch *Chain) SendFromL2ToL2AccountNativeTokens(id iotago.NativeTokenID, target isc.AgentID, amount interface{}, user *cryptolib.KeyPair) error

func (*Chain) ServersUpdated added in v1.0.3

func (*Chain) ServersUpdated(serverNodes []*cryptolib.PublicKey)

ServersUpdated implements chain.Chain

func (*Chain) SetGasFeePolicy added in v1.0.3

func (ch *Chain) SetGasFeePolicy(user *cryptolib.KeyPair, fp *gas.FeePolicy)

func (*Chain) SetGasLimits added in v1.0.3

func (ch *Chain) SetGasLimits(user *cryptolib.KeyPair, gl *gas.Limits)

func (*Chain) Store added in v1.0.3

func (ch *Chain) Store() indexedstore.IndexedStore

Store implements chain.Chain

func (*Chain) String

func (ch *Chain) String() string

String is string representation for main parameters of the chain

func (*Chain) Sync added in v0.2.3

func (ch *Chain) Sync()

Sync runs all ready requests

func (*Chain) TransferAllowanceTo added in v0.3.0

func (ch *Chain) TransferAllowanceTo(
	allowance *isc.Assets,
	targetAccount isc.AgentID,
	wallet *cryptolib.KeyPair,
	nft ...*isc.NFT,
) error

TransferAllowanceTo sends an on-ledger request to transfer funds to target account (sends extra base tokens to the sender account to cover gas)

func (*Chain) UploadBlob

func (ch *Chain) UploadBlob(user *cryptolib.KeyPair, params ...interface{}) (ret hashing.HashValue, err error)

UploadBlob calls core 'blob' smart contract blob.FuncStoreBlob entry point to upload blob data to the chain. It returns hash of the blob, the unique identifier of it. The parameters must be either a dict.Dict, or a sequence of pairs 'fieldName': 'fieldValue' Requires at least 2 x gasFeeEstimate to be on sender's L2 account

func (*Chain) UploadBlobFromFile added in v0.3.0

func (ch *Chain) UploadBlobFromFile(keyPair *cryptolib.KeyPair, fileName, fieldName string, params ...interface{}) (hashing.HashValue, error)

UploadBlobFromFile uploads blob from file data in the specified blob field plus optional other fields

func (*Chain) UploadWasm

func (ch *Chain) UploadWasm(keyPair *cryptolib.KeyPair, binaryCode []byte) (ret hashing.HashValue, err error)

UploadWasm is a syntactic sugar of the UploadBlob used to upload Wasm binary to the chain.

parameter 'binaryCode' is the binary of Wasm smart contract program

The blob for the Wasm binary used fixed field names which are statically known by the 'root' smart contract which is responsible for the deployment of contracts on the chain

func (*Chain) UploadWasmFromFile

func (ch *Chain) UploadWasmFromFile(keyPair *cryptolib.KeyPair, fileName string) (hashing.HashValue, error)

UploadWasmFromFile is a syntactic sugar to upload file content as blob data to the chain

func (*Chain) WaitForRequestsMark added in v1.0.3

func (ch *Chain) WaitForRequestsMark()

WaitForRequestsMark marks the amount of requests processed until now This allows the WaitForRequestsThrough() function to wait for the specified of number of requests after the mark point.

func (*Chain) WaitForRequestsThrough added in v0.2.0

func (ch *Chain) WaitForRequestsThrough(numReq int, maxWait ...time.Duration) bool

WaitForRequestsThrough waits until the specified number of requests have been processed since the last call to WaitForRequestsMark()

func (*Chain) WaitUntil added in v0.2.3

func (ch *Chain) WaitUntil(p func() bool, maxWait ...time.Duration) bool

WaitUntil waits until the condition specified by the given predicate yields true

func (*Chain) WaitUntilMempoolIsEmpty added in v0.3.0

func (ch *Chain) WaitUntilMempoolIsEmpty(timeout ...time.Duration) bool

func (*Chain) Withdraw added in v1.0.3

func (ch *Chain) Withdraw(assets *isc.Assets, user *cryptolib.KeyPair) error

Withdraw sends assets from the L2 account to L1

type InitChainOptions added in v0.3.0

type InitChainOptions struct {
	// optional parameters for the chain origin
	OriginParameters dict.Dict
	// optional VMRunner. Default is StardustVM
	VMRunner vm.VMRunner
	// flag forces bypassing any StardustVM ledger-dependent calls, such as init or blocklog
	// To be used with provided non-standard VMRunner
	BypassStardustVM bool
}

type InitOptions added in v0.3.0

type InitOptions struct {
	AutoAdjustStorageDeposit bool
	Debug                    bool
	PrintStackTrace          bool
	Seed                     cryptolib.Seed
	Log                      *logger.Logger
}

func DefaultInitOptions added in v1.0.3

func DefaultInitOptions() *InitOptions

type L1L2AddressAssets added in v0.3.0

type L1L2AddressAssets struct {
	Address  iotago.Address
	AssetsL1 *isc.Assets
	AssetsL2 *isc.Assets
}

func (*L1L2AddressAssets) String added in v0.3.0

func (a *L1L2AddressAssets) String() string

type Mempool added in v1.0.3

type Mempool interface {
	ReceiveRequests(reqs ...isc.Request)
	RequestBatchProposal() []isc.Request
	RemoveRequests(reqs ...isc.RequestID)
	Info() MempoolInfo
}

type MempoolInfo added in v1.0.3

type MempoolInfo struct {
	TotalPool      int
	InPoolCounter  int
	OutPoolCounter int
}

type NFTMintedInfo added in v0.3.0

type NFTMintedInfo struct {
	Output   iotago.Output
	OutputID iotago.OutputID
	NFTID    iotago.NFTID
}

type Solo

type Solo struct {
	// instance of the test
	T TestContext
	// contains filtered or unexported fields
}

Solo is a structure which contains global parameters of the test: one per test instance

func New

func New(t TestContext, initOptions ...*InitOptions) *Solo

New creates an instance of the Solo environment If solo is used for unit testing, 't' should be the *testing.T instance; otherwise it can be either nil or an instance created with NewTestContext.

func (*Solo) AddRequestsToChainMempool added in v0.3.0

func (env *Solo) AddRequestsToChainMempool(ch *Chain, reqs []isc.Request)

func (*Solo) AddRequestsToChainMempoolWaitUntilInbufferEmpty added in v0.3.0

func (env *Solo) AddRequestsToChainMempoolWaitUntilInbufferEmpty(ch *Chain, reqs []isc.Request, timeout ...time.Duration)

AddRequestsToChainMempoolWaitUntilInbufferEmpty adds all the requests to the chain mempool, then waits for the in-buffer to be empty, before resuming VM execution

func (*Solo) AddToLedger

func (env *Solo) AddToLedger(tx *iotago.Transaction) error

AddToLedger adds (synchronously confirms) transaction to the UTXODB ledger. Return error if it is invalid or double spend

func (*Solo) AdvanceClockBy

func (env *Solo) AdvanceClockBy(step time.Duration)

AdvanceClockBy advances logical clock by time step

func (*Solo) AssertL1BaseTokens added in v0.3.0

func (env *Solo) AssertL1BaseTokens(addr iotago.Address, expected uint64)

func (*Solo) AssertL1NativeTokens added in v0.3.0

func (env *Solo) AssertL1NativeTokens(addr iotago.Address, nativeTokenID iotago.NativeTokenID, expected interface{})

func (*Solo) EnqueueRequests

func (env *Solo) EnqueueRequests(tx *iotago.Transaction)

EnqueueRequests adds requests contained in the transaction to mempools of respective target chains

func (*Solo) GetFundsFromFaucet added in v0.3.0

func (env *Solo) GetFundsFromFaucet(target iotago.Address, amount ...uint64) (*iotago.Transaction, error)

func (*Solo) GetUnspentOutputs added in v0.3.0

func (env *Solo) GetUnspentOutputs(addr iotago.Address) (iotago.OutputSet, iotago.OutputIDs)

func (*Solo) GlobalTime added in v0.3.0

func (env *Solo) GlobalTime() time.Time

GlobalTime return current logical clock time on the 'solo' instance

func (*Solo) HasL1NFT added in v0.3.0

func (env *Solo) HasL1NFT(addr iotago.Address, id *iotago.NFTID) bool

func (*Solo) L1Assets added in v0.3.0

func (env *Solo) L1Assets(addr iotago.Address) *isc.Assets

L1Assets returns all ftokens of the address contained in the UTXODB ledger

func (*Solo) L1BaseTokens added in v0.3.0

func (env *Solo) L1BaseTokens(addr iotago.Address) uint64

func (*Solo) L1Ledger added in v0.3.0

func (env *Solo) L1Ledger() *utxodb.UtxoDB

func (*Solo) L1NFTs added in v0.3.0

func (env *Solo) L1NFTs(addr iotago.Address) map[iotago.OutputID]*iotago.NFTOutput

func (*Solo) L1NativeTokens added in v0.3.0

func (env *Solo) L1NativeTokens(addr iotago.Address, nativeTokenID iotago.NativeTokenID) *big.Int

L1NativeTokens returns number of native tokens contained in the given address on the UTXODB ledger

func (*Solo) MintNFTL1 added in v0.3.0

func (env *Solo) MintNFTL1(issuer *cryptolib.KeyPair, target iotago.Address, immutableMetadata []byte) (*isc.NFT, *NFTMintedInfo, error)

MintNFTL1 mints a single NFT with the `issuer` account and sends it to a `target` account. Base tokens in the NFT output are sent to the minimum storage deposit and are taken from the issuer account.

func (*Solo) MintNFTsL1 added in v1.0.3

func (env *Solo) MintNFTsL1(issuer *cryptolib.KeyPair, target iotago.Address, collectionOutputID *iotago.OutputID, immutableMetadata [][]byte) ([]*isc.NFT, []*NFTMintedInfo, error)

MintNFTsL1 mints len(immutableMetadata) NFTs with the `issuer` account and sends them to a `target` account.

If collectionOutputID is not nil, it must be an outputID of an NFTOutput owned by the issuer. All minted NFTs will belong to the given collection. See: https://github.com/iotaledger/tips/blob/main/tips/TIP-0027/tip-0027.md

Base tokens in the NFT outputs are sent to the minimum storage deposit and are taken from the issuer account.

func (*Solo) NewChain

func (env *Solo) NewChain() *Chain

NewChain deploys new default chain instance.

func (*Solo) NewChainExt added in v0.3.0

func (env *Solo) NewChainExt(chainOriginator *cryptolib.KeyPair, initBaseTokens uint64, name string, initOptions ...InitChainOptions) (*Chain, *iotago.Transaction)

func (*Solo) NewKeyPair added in v0.2.0

func (env *Solo) NewKeyPair(seedOpt ...*cryptolib.Seed) (*cryptolib.KeyPair, iotago.Address)

NewSignatureSchemeAndPubKey generates new ed25519 signature scheme Returns signature scheme interface and public key in binary form

func (*Solo) NewKeyPairFromIndex added in v0.3.0

func (env *Solo) NewKeyPairFromIndex(index int) *cryptolib.KeyPair

func (*Solo) NewKeyPairWithFunds added in v0.2.0

func (env *Solo) NewKeyPairWithFunds(seed ...*cryptolib.Seed) (*cryptolib.KeyPair, iotago.Address)

NewSignatureSchemeWithFundsAndPubKey generates new ed25519 signature scheme and requests some tokens from the UTXODB faucet. The amount of tokens is equal to utxodb.FundsFromFaucetAmount (=1000Mi) base tokens Returns signature scheme interface and public key in binary form

func (*Solo) NewSeedFromIndex added in v0.2.0

func (env *Solo) NewSeedFromIndex(index int) *cryptolib.Seed

func (*Solo) Publisher added in v1.0.3

func (env *Solo) Publisher() *publisher.Publisher

func (*Solo) RequestsForChain added in v0.2.0

func (env *Solo) RequestsForChain(tx *iotago.Transaction, chainID isc.ChainID) ([]isc.Request, error)

RequestsForChain parses the transaction and returns all requests contained in it which have chainID as the target

func (*Solo) SendL1 added in v1.0.3

func (env *Solo) SendL1(targetAddress iotago.Address, assets *isc.Assets, wallet *cryptolib.KeyPair)

SendL1 sends base or native tokens to another L1 address

func (*Solo) SyncLog added in v0.2.0

func (env *Solo) SyncLog()

func (*Solo) UnspentOutputs added in v0.3.0

func (env *Solo) UnspentOutputs(addr iotago.Address) (iotago.OutputSet, iotago.OutputIDs)

func (*Solo) WithNativeContract added in v0.2.0

func (env *Solo) WithNativeContract(c *coreutil.ContractProcessor) *Solo

WithNativeContract registers a native contract so that it may be deployed

type TestContext added in v0.2.0

type TestContext interface {
	Name() string
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	FailNow()
	Log(args ...interface{})
	Logf(format string, args ...interface{})
}

TestContext is a subset of the interface provided by *testing.T and require.TestingT It allows to use Solo outside of unit tests.

func NewTestContext added in v0.2.0

func NewTestContext(name string) TestContext

Directories

Path Synopsis
package solobench provides tools to benchmark contracts running under solo
package solobench provides tools to benchmark contracts running under solo

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL